>  기사  >  백엔드 개발  >  떠날 시간인가? 재건할 시간입니다! 트위터 만들기

떠날 시간인가? 재건할 시간입니다! 트위터 만들기

王林
王林원래의
2024-08-12 22:34:351044검색

Musk와 Twitter에 지친 사용자를 위한 새로운 소셜 네트워크의 가장 중요한 기능은 다음과 같습니다.

  • 트위터 archive.zip 파일 가져오기
  • 쉽게 가입하세요
  • 동일하지는 않더라도 유사한 사용자 기능

덜 중요하지만 확실히 유용한 플랫폼 기능

  • 윤리적으로 수익을 창출하고 조정합니다
  • AI를 활용하여 문제가 있는 콘텐츠를 식별하세요
  • Onfido 또는 SMART ID 서비스 사용 시 파란색 진드기

이 게시물에서는 첫 번째 기능에 중점을 두겠습니다. 트위터 archive.zip 파일을 가져오는 중입니다.

파일

Twitter에서는 데이터를 쉽게 얻을 수 있도록 만들지 않았습니다. 그들이 당신에게 그것에 대한 액세스를 제공한다는 것은 좋은 일입니다(법적으로는 그렇게 해야 합니다). 형식이 쓰레기네요.

실제로는 미니 웹 아카이브로 제공되며 모든 데이터는 JavaScript 파일에 갇혀 있습니다. 편리한 데이터 저장이라기보다 웹앱에 가깝습니다.

Your archive.html 파일을 열면 다음과 같은 내용이 나타납니다.

Time to Leave? Time to Rebuild! Making Twitter

참고: 저는 사이트에 Next.js를 사용하고 백엔드에 Go와 GraphQL을 사용하여 구축하기로 꽤 일찍 결정했습니다.

그럼 데이터가 구조화된 데이터가 아닌 경우에는 어떻게 하나요?

글쎄요, 분석해 보세요.

기본 Go 스크립트 만들기

Go를 시작하는 방법과 프로젝트 디렉터리를 설정하는 방법에 대한 공식 문서를 살펴보세요.

이 과정을 함께 해킹해보겠습니다. TwitterX에 너무 애착을 느끼는 사람들을 끌어들이는 가장 중요한 기능 중 하나인 것 같습니다.

첫 번째 단계는 main.go 파일을 만드는 것입니다. 이 파일에서 우리는 가서(하) 몇 가지 작업을 수행할 것입니다.

  • os.Args: 명령줄 인수를 보유하는 슬라이스입니다.
  • os.Args[0]은 프로그램 이름이고, os.Args[1]은 프로그램에 전달되는 첫 번째 인수입니다.
  • 인수 확인: 이 함수는 인수가 하나 이상 제공되었는지 확인합니다. 그렇지 않은 경우 경로를 묻는 메시지를 인쇄합니다.
  • 실행 함수: 이 함수는 현재로서는 단순히 전달된 경로를 인쇄합니다.
으아악

모든 단계에서 다음과 같이 파일을 실행합니다.

으아악

트위터 아카이브 내보내기가 없으면 간단한 매니페스트.js 파일을 만들고 다음 JavaScript를 제공하세요.

으아악

전체적으로 사용할 twitter.zip 파일로 압축하세요.

Zip 파일 읽기

다음 단계는 zip 파일의 내용을 읽는 것입니다. 우리는 이 작업을 최대한 효율적으로 수행하고 디스크에서 데이터가 추출되는 시간을 줄이고 싶습니다.

zip에는 압축을 풀 필요가 없는 파일도 많습니다.

main.go 파일을 편집하겠습니다.

  • ZIP 파일 열기: zip.OpenReader() 함수는 경로에 지정된 ZIP 파일을 여는 데 사용됩니다.
  • 파일 반복: 함수는 zip.File의 조각인 r.File을 사용하여 ZIP 아카이브의 각 파일을 반복합니다. 각 파일의 Name 속성이 인쇄됩니다.
으아악

JS만! 우리는 구조화된 데이터를 찾고 있습니다.

이 아카이브 파일은 심각하게 도움이 되지 않습니다. 우리는 .js 파일만 확인하고 /data 디렉터리에서만 확인하려고 합니다.

  • ZIP 파일 열기: zip.OpenReader()를 사용하여 ZIP 파일을 엽니다.
  • /data 디렉터리 확인: 프로그램은 ZIP 아카이브의 파일을 반복합니다. strings.HasPrefix(f.Name, "data/")를 사용하여 파일이 /data 디렉터리에 있는지 확인합니다.
  • .js 파일 찾기: 프로그램은 filepath.Ext(f.Name)을 사용하여 파일에 .js 확장자가 있는지도 확인합니다.
  • 내용 읽기 및 인쇄: /data 디렉터리에 .js 파일이 있으면 프로그램이 해당 내용을 읽고 인쇄합니다.
으아악

JS를 파싱하세요! 우리는 그 데이터를 원한다

구조화된 데이터를 찾았습니다. 이제 이를 구문 분석해야 합니다. 좋은 소식은 Go 내부에서 JavaScript를 사용하기 위한 기존 패키지가 있다는 것입니다. 우리는 고자를 사용할 거예요

이 섹션에서 Goja에 익숙하고 파일의 출력을 본 적이 있다면 앞으로 오류가 발생할 것임을 알 수 있습니다.

고자 설치:

으아악

이제 main.go 파일을 편집하여 다음을 수행하겠습니다.

  • goja로 구문 분석: goja.New() 함수는 새로운 JavaScript 런타임을 생성하고 vm.RunString(processedContents)은 해당 런타임 내에서 처리된 JavaScript 코드를 실행합니다.
  • 파싱 오류 처리
으아악

놀랐어요. window is not Defined(창이 정의되지 않았습니다)는 익숙한 오류일 수 있습니다. 기본적으로 goja는 EMCA 런타임을 실행합니다. 창은 브라우저 컨텍스트이므로 안타깝게도 사용할 수 없습니다.

실제로 JS를 구문 분석합니다.

이 시점에서 몇 가지 문제를 겪었습니다. 최상위 JS 파일이기 때문에 데이터를 반환할 수 없다는 점을 포함합니다.

간단히 말하면, 파일을 런타임에 로드하기 전에 파일 내용을 수정해야 합니다.

main.go 파일을 수정해 보겠습니다.

  • reConfig: A regex that matches any assignment of the form window.someVariable = { and replaces it with var data = {.
  • reArray: A regex that matches any assignment of the form window.someObject.someArray = [ and replaces it with var data = [
  • Extracting data: Running the script, we use vm.Get("data") to retrieve the value of the data variable from the JavaScript context.
package main

import (
    "archive/zip"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "regexp"
    "strings"

    "github.com/dop251/goja"
)

func readFile(file *zip.File) {
    // Open the file inside the zip
    rc, err := file.Open()
    if err != nil {
        log.Fatal(err)
    }
    defer rc.Close()

    // Read the contents of the file
    contents, err := ioutil.ReadAll(rc)
    if err != nil {
        log.Fatal(err)
    }

    // Regular expressions to replace specific patterns
    reConfig := regexp.MustCompile(`window\.\w+\s*=\s*{`)
    reArray := regexp.MustCompile(`window\.\w+\.\w+\.\w+\s*=\s*\[`)

    // Replace patterns in the content
    processedContents := reConfig.ReplaceAllStringFunc(string(contents), func(s string) string {
        return "var data = {"
    })
    processedContents = reArray.ReplaceAllStringFunc(processedContents, func(s string) string {
        return "var data = ["
    })

    // Parse the JavaScript file using goja
    vm := goja.New()
    _, err = vm.RunString(processedContents)
    if err != nil {
        log.Fatalf("Error parsing JS file: %v", err)
    }

    // Retrieve the value of the 'data' variable from the JavaScript context
    value := vm.Get("data")
    if value == nil {
        log.Fatalf("No data variable found in the JS file")
    }

    // Output the parsed data
    fmt.Printf("Processed JavaScript file: %s\n", file.Name)
    fmt.Printf("Data extracted: %v\n", value.Export())
}

func run(path string) {
    // Open the zip file
    r, err := zip.OpenReader(path)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    for _, f := range r.File {
        // Check if the file is in the /data directory and has a .js extension
        if strings.HasPrefix(f.Name, "data/") && strings.ToLower(filepath.Ext(f.Name)) == ".js" {
            readFile(f)
            return // Exit after processing the first .js file so we don't end up printing a gazillion lines when testing
        }
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    path:= os.Args[1]
    run(path)
}

Hurrah. Assuming I didn't muck up the copypaste into this post, you should now see a rather ugly print of the struct data from Go.

JSON would be nice

Edit the main.go file to marshall the JSON output.

  • Use value.Export() to get the data from the struct
  • Use json.MarshallIndent() for pretty printed JSON (use json.Marshall if you want to minify the output).
package main

import (
    "archive/zip"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "regexp"
    "strings"

    "github.com/dop251/goja"
)

func readFile(file *zip.File) {
    // Open the file inside the zip
    rc, err := file.Open()
    if err != nil {
        log.Fatal(err)
    }
    defer rc.Close()

    // Read the contents of the file
    contents, err := ioutil.ReadAll(rc) // deprecated :/
    if err != nil {
        log.Fatal(err)
    }

    // Regular expressions to replace specific patterns
    reConfig := regexp.MustCompile(`window\.\w+\s*=\s*{`)
    reArray := regexp.MustCompile(`window\.\w+\.\w+\.\w+\s*=\s*\[`)

    // Replace patterns in the content
    processedContents := reConfig.ReplaceAllStringFunc(string(contents), func(s string) string {
        return "var data = {"
    })
    processedContents = reArray.ReplaceAllStringFunc(processedContents, func(s string) string {
        return "var data = ["
    })

    // Parse the JavaScript file using goja
    vm := goja.New()
    _, err = vm.RunString(processedContents)
    if err != nil {
        log.Fatalf("Error parsing JS file: %v", err)
    }

    // Retrieve the value of the 'data' variable from the JavaScript context
    value := vm.Get("data")
    if value == nil {
        log.Fatalf("No data variable found in the JS file")
    }

    // Convert the data to a Go-native type
    data := value.Export()

    // Marshal the Go-native type to JSON
    jsonData, err := json.MarshalIndent(data, "", "  ")
    if err != nil {
        log.Fatalf("Error marshalling data to JSON: %v", err)
    }

    // Output the JSON data
    fmt.Println(string(jsonData))
}

func run(zipFilePath string) {
    // Open the zip file
    r, err := zip.OpenReader(zipFilePath)
    if err != nil {
        log.Fatal(err)
    }
    defer r.Close()

    // Iterate through the files in the zip archive
    for _, f := range r.File {
        // Check if the file is in the /data directory and has a .js extension
        if strings.HasPrefix(f.Name, "data/") && strings.ToLower(filepath.Ext(f.Name)) == ".js" {
            readFile(f)
            return // Exit after processing the first .js file
        }
    }
}

func main() {
    // Example usage
    if len(os.Args) < 2 {
        log.Fatal("Please provide the path to the zip file as an argument.")
    }

    zipFilePath := os.Args[1]
    run(zipFilePath)
}

That's it!

go run main.go twitter.zip
}
  "userInfo": {
    "accountId": "1234567890",
    "displayName": "Luke ✨",
    "userName": "lukeocodes"
  }
}

Open source

I'll be open sourcing a lot of this work so that others who want to parse the data from the archive, can store it how they like.

위 내용은 떠날 시간인가? 재건할 시간입니다! 트위터 만들기의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.