深入淺出爬蟲之道: Python、Golang與GraphQuery的對比

本文將分別使用 Python 怠李,Golang 以及 GraphQuery 來解析某網(wǎng)站的 素材詳情頁面 ,這個頁面的特色是具有清晰的數(shù)據(jù)結(jié)構(gòu),但是DOM結(jié)構(gòu)不夠規(guī)范捺癞,無法通過單獨的選擇器定位頁面元素夷蚊,對頁面的解析造成了一些曲折。通過這個頁面的解析過程髓介,深入淺出的了解爬蟲的解析思想與這些語言之間的異同惕鼓。

一一膨、前言

在前言中呀邢,為了防止在后面的章節(jié)產(chǎn)生不必要的困擾,我們將會首先了解一些基本的編程理念汞幢。

1. 語義化的DOM結(jié)構(gòu)

這里我們講的語義化的DOM結(jié)構(gòu)驼鹅,不僅僅包括 語義化的html標簽微谓,也包括了語義化的選擇器森篷,在前端開發(fā)中應(yīng)該注意的是,所有的動態(tài)文本都應(yīng)該有單獨的 html 標簽包裹豺型,并最好賦予其語義化的 class 屬性或 id 屬性仲智,這在版本功能的迭代中,對前端和后端的開發(fā)都是大有裨益的姻氨,比如下面的HTML代碼:

<div class="main-right fr">
    <p>編號:32490230</p>
    <p class="main-rightStage">模式:RGB</p>
    <p class="main-rightStage">體積:16.659 MB</p>
    <p class="main-rightStage">分辨率:72dpi</p>
</div>

這就是不夠語義化的前端代碼钓辆,32504070RGB肴焊,16.659 MB前联,72dpi這些值都是動態(tài)屬性, 會跟隨編號的改變而改變娶眷,在規(guī)范的開發(fā)中似嗤,應(yīng)該將這些 動態(tài)變化的屬性,分別用 <span> 這類行內(nèi)標簽包裹起來届宠,并賦予其一定的語義化選擇器烁落,在上面的HTML結(jié)構(gòu)中大致可以推測出這是后端直接使用 foreach 渲染出的頁面,這是不符合前后端分離的思想的豌注,如果有一天他們決定使用 jsonpAjax 渲染這些屬性伤塌, 由前端進行渲染,工作量無疑會上一個層次轧铁。語義化的DOM結(jié)構(gòu)更傾向于下面這樣:

<p class="main-rightStage property-mode">
    模式:<span>RGB</span>
</p>

也可以將 property-mode 直接作為 spanclass 屬性每聪,這樣這些屬性無論是后端渲染,還是前端動態(tài)渲染都減輕了產(chǎn)品迭代產(chǎn)生的負擔(dān)。

2. 穩(wěn)定的解析代碼

語義化的DOM結(jié)構(gòu) 之后药薯,我們來談?wù)劮€(wěn)定的解析代碼他爸, 對于下面的DOM結(jié)構(gòu):

<div class="main-right fr">
    <p>編號:32490230</p>
    <p class="main-rightStage">模式:RGB</p>
    <p class="main-rightStage">體積:16.659 MB</p>
    <p class="main-rightStage">分辨率:72dpi</p>
</div>

如果我們想要提取 模式 信息,當然可以采取下面的步驟:

  1. 選取 class 屬性中包含 main-rightdiv
  2. 選取這個 div 中第二個 p 元素果善,取出其包含的文本
  3. 刪除文本中的 模式:诊笤, 得到模式為 RGB

雖然成功獲取到了想要的結(jié)果,但是這樣的解析方法巾陕,我們認為它是 不穩(wěn)定的讨跟,這個不穩(wěn)定是指 在其祖先元素、兄弟元素等自身以外的元素節(jié)點發(fā)生一定程度的結(jié)構(gòu)改變時鄙煤,導(dǎo)致解析錯誤或失敗 的情況晾匠, 比如如果有一天在 模式 所在的節(jié)點之前增加了一個 尺寸 的屬性:

<div class="main-right fr">
    <p>編號:32490230</p>
    <p class="main-rightStage">尺寸:4724×6299像素</p>
    <p class="main-rightStage">模式:RGB</p>
    <p class="main-rightStage">體積:16.659 MB</p>
    <p class="main-rightStage">分辨率:72dpi</p>
</div>

那么我們之前的解析將會發(fā)生錯誤(什么?你覺得不可能發(fā)生這樣的變動梯刚?請對比 Page1Page2)凉馆。
那我們應(yīng)該如何寫出更穩(wěn)定的解析代碼呢,對于上面的DOM結(jié)構(gòu)亡资,我們可以有下面幾種思路:
思路一: 遍歷 class 屬性為 main-rightStagep 節(jié)點澜共,依次判斷節(jié)點的文本是否以 模式 開頭, 如果是锥腻, 取出其 后的內(nèi)容嗦董,缺點是邏輯太多,不易維護且降低了代碼可讀性瘦黑。
思路二: 使用正則表達式 模式:([A-Z]+) 進行匹配京革,缺點是使用不當可能造成效率問題。
思路三: 使用 CSS選擇器中的 contains 方法幸斥,比如 .main-rightStage:contains(模式)匹摇, 就可以選取文本中包含 模式,且 class 屬性中包含 main-rightStage 的節(jié)點了甲葬。但缺點是不同語言和不同庫對這種語法的支持程度各有不同廊勃,缺乏兼容性。
使用哪種方法演顾,仁者見仁智者見智供搀,不同的解析思路帶來的解析的 穩(wěn)定性、代碼的 復(fù)雜程度钠至、運行效率兼容性 都是不同的葛虐, 開發(fā)者需要從各種因素中進行權(quán)衡, 來寫出最優(yōu)秀的解析代碼棉钧。

二屿脐、進行頁面的解析

在進行頁面數(shù)據(jù)的抽取之前,首先要做的是明確我們需要哪些數(shù)據(jù)、頁面上提供了哪些數(shù)據(jù)的诵,然后設(shè)計出我們需要的數(shù)據(jù)結(jié)構(gòu)万栅。首先打開 待解析頁面, 由于其最上方的 瀏覽量西疤、收藏量烦粒、下載量等數(shù)據(jù)是動態(tài)加載的, 在我們的演示中暫時不需要代赁,而這個頁面右邊的 尺寸扰她、模式 等數(shù)據(jù),通過上面 Page1Page2 的對比芭碍,可以得知這些屬性是不一定存在的徒役,因此將它們一起歸到 metainfo 中。因此我們需要獲得的數(shù)據(jù)如下圖所示:

datastruct.png

由此我們可以很快設(shè)計出我們的數(shù)據(jù)結(jié)構(gòu):

{
    title
    pictype
    number
    type
    metadata {
        size
        volume
        mode
        resolution
    }
    author
    images []
    tags []
}

其中 size窖壕、volume忧勿、moderesolution由于可能不存在瞻讽,因此歸入到了 metadata 下鸳吸, images 是一個圖片地址的數(shù)組卸夕,tags 是標簽數(shù)組,在確定了要提取的數(shù)據(jù)結(jié)構(gòu)快集,就可以開始進行解析廉白。

使用Python進行頁面的解析

Python庫的數(shù)量非常龐大,有很多優(yōu)秀的庫可以幫助到我們猴蹂,在使用Python進行頁面的解析時院溺,我們通常用到下面這些庫:

  1. 提供 正則表達式 支持的 re
  2. 提供 CSS選擇器 支持的 pyquerybeautifulsoup4
  3. 提供 Xpath 支持的 lxml
  4. 提供 JSON PATH 支持的 jsonpath_rw

這些庫在 Python 3 下獲得支持的,可以通過 pip install 進行安裝磅轻。
由于 CSS選擇器 的語法比 Xpath 語法要更加簡潔珍逸,而在方法的調(diào)用上,pyquerybeautifulsoup4 要更加方便聋溜,因此在 2 和 3 之間我們選擇了 pyquery谆膳。
下面我們會以 titletype 屬性的獲取作為例子進行講解, 其他節(jié)點的獲取是同理的撮躁。首先我們先使用 requests 庫下載這個頁面的源文件:

import requests
from pyquery import PyQuery as pq
response = requests.get("http://www.58pic.com/newpic/32504070.html")
document = pq(response.content.decode('gb2312'))

下面使用Python進行的解析都將依次為前提進行漱病。

1. 獲取title節(jié)點

打開 待解析頁面,在標題上右鍵, 點擊 查看元素杨帽,可以看到它的DOM結(jié)構(gòu)如下:

title.png

這時我們注意到漓穿, 我們想要提取出的標題文本 大俠海報金庸武俠水墨中國風(fēng)黑白,并沒有被html標簽包裹注盈,這是不符合我們上面提到的 語義化的dom結(jié)構(gòu) 的晃危。同時,使用CSS選擇器老客,也是無法直接選取到這個文本節(jié)點的(可以使用Xpath直接選取到山害,本文略)。對于這樣的節(jié)點沿量,我們可以有下面兩種思路:
思路一: 先選取其父元素節(jié)點浪慌, 獲取其 HTML 內(nèi)容,使用正則表達式朴则, 匹配在 </div><p 之間的文本权纤。
思路二: 先選取其父元素節(jié)點,然后刪除文本節(jié)點之外的其他節(jié)點乌妒,再直接通過獲取父元素節(jié)點的文本汹想,得到想要的標題文本。
我們采取思路二撤蚊,寫出下面的Python代碼:

title_node = document.find(".detail-title")
title_node.find("div").remove()
title_node.find("p").remove()
print(title_node.text())

輸出結(jié)果與我們期望的相同古掏, 為 大俠海報金庸武俠水墨中國風(fēng)黑白

2. 獲取size節(jié)點

尺寸 上右鍵查看元素侦啸,可以看到下圖所示的DOM結(jié)構(gòu):

metainfo.png

我們發(fā)現(xiàn)這些節(jié)點不具有語義化的選擇器槽唾,并且這些屬性不一定都存在(詳見Page1Page2 的對比)。在 穩(wěn)定的解析代碼 中我們也講到了對于這種結(jié)構(gòu)的文檔可以采取的幾種思路光涂,這里我們采用正則解析的方法:

import re
context = document.find(".mainRight-file").text()
file_type_matches = re.compile("尺寸:(.*?像素)").findall(context)
filetype = ""
if len(file_type_matches) > 0:
    filetype =  file_type_matches[0]
print(filetype)

由于獲取 size庞萍、volumemode忘闻、resolution 這些屬性钝计,都可以采取類似的方法,因此我們可以歸結(jié)出一個正則提取的函數(shù):

def regex_get(text, expr):
    matches = re.compile(expr).findall(text)
    if len(matches) == 0:
        return ""
    return matches[0]

因此齐佳,在獲取 size 節(jié)點時私恬,我們的代碼就可以精簡為:

size = regex_get(context, r"尺寸:(.*?像素)")

3. 完整的Python代碼

到這里,我們解析頁面可能遇到的問題就已經(jīng)解決了大半炼吴,整個Python代碼如下:

import requests
import re
from pyquery import PyQuery as pq

def regex_get(text, expr):
    matches = re.compile(expr).findall(text)
    if len(matches) == 0:
        return ""
    return matches[0]

conseq = {}

## 下載文檔
response = requests.get("http://www.58pic.com/newpic/32504070.html")
document = pq(response.text)

## 獲取文件標題
title_node = document.find(".detail-title")
title_node.find("div").remove()
title_node.find("p").remove()
conseq["title"] = title_node.text()

## 獲取素材類型
conseq["pictype"] = document.find(".pic-type").text()

## 獲取文件格式
conseq["filetype"] =  regex_get(document.find(".mainRight-file").text(), r"文件格式:([a-z]+)")

## 獲取元數(shù)據(jù)
context = document.find(".main-right p").text()
conseq['metainfo'] = {
    "size": regex_get(context, r"尺寸:(.*?像素)"),
    "volume": regex_get(context, r"體積:(.*? MB)"),
    "mode": regex_get(context, r"模式:([A-Z]+)"),
    "resolution": regex_get(context, r"分辨率:(\d+dpi)"),
}

## 獲取作者
conseq['author'] = document.find('.user-name').text()

## 獲取圖片
conseq['images'] = []
for node_image in document.find("#show-area-height img"):
    conseq['images'].append(pq(node_image).attr("src"))

## 獲取tag
conseq['tags'] = []
for node_image in document.find(".mainRight-tagBox .fl"):
    conseq['tags'].append(pq(node_image).text())

print(conseq)

使用Golang進行頁面的解析

Golang 中解析 htmlxml 文檔本鸣, 常用到的庫有以下幾種:

  1. 提供 正則表達式 支持的 regexp
  2. 提供 CSS選擇器 支持的 github.com/PuerkitoBio/goquery
  3. 提供 Xpath 支持的 gopkg.in/xmlpath.v2
  4. 提供 JSON PATH 支持的 github.com/tidwall/gjson

這些庫,你都可以通過 go get -u 來獲取缺厉,由于在上面的Python解析中我們已經(jīng)整理出了解析邏輯永高,在Golang中只需要復(fù)現(xiàn)即可隧土,與 Python 不同的是曹傀,我們最好先為我們的數(shù)據(jù)結(jié)構(gòu)定義一個 struct,像下面這樣:

type Reuslt struct {
    Title    string
    Pictype  string
    Number   string
    Type     string
    Metadata struct {
        Size       string
        Volume     string
        Mode       string
        Resolution string
    }
    Author string
    Images []string
    Tags   []string
}

同時皆愉,由于我們的 待解析頁面 是非主流的 gbk 編碼幕庐,所以在下載下來文檔之后,需要手動將 utf-8 的編碼轉(zhuǎn)換為 gbk 的編碼异剥,這個過程雖然不在解析的范疇之內(nèi)絮重,但是也是必須要做的步驟之一, 我們使用了 github.com/axgle/mahonia 這個庫進行編碼的轉(zhuǎn)換督怜,并整理出了編碼轉(zhuǎn)換的函數(shù) decoderConvert

func decoderConvert(name string, body string) string {
    return mahonia.NewDecoder(name).ConvertString(body)
}

因此狠角, 最終的 golang 代碼應(yīng)該是下面這樣的:

package main

import (
    "encoding/json"
    "log"
    "regexp"
    "strings"

    "github.com/axgle/mahonia"
    "github.com/parnurzeal/gorequest"

    "github.com/PuerkitoBio/goquery"
)

type Reuslt struct {
    Title    string
    Pictype  string
    Number   string
    Type     string
    Metadata struct {
        Size       string
        Volume     string
        Mode       string
        Resolution string
    }
    Author string
    Images []string
    Tags   []string
}

func RegexGet(text string, expr string) string {
    regex, _ := regexp.Compile(expr)
    return regex.FindString(text)
}

func decoderConvert(name string, body string) string {
    return mahonia.NewDecoder(name).ConvertString(body)
}

func main() {
    //下載文檔
    request := gorequest.New()
    _, body, _ := request.Get("http://www.58pic.com/newpic/32504070.html").End()
    document, err := goquery.NewDocumentFromReader(strings.NewReader(decoderConvert("gbk", body)))
    if err != nil {
        panic(err)
    }
    conseq := &Reuslt{}
    //獲取文件標題
    titleNode := document.Find(".detail-title")
    titleNode.Find("div").Remove()
    titleNode.Find("p").Remove()
    conseq.Title = titleNode.Text()

    // 獲取素材類型
    conseq.Pictype = document.Find(".pic-type").Text()
    // 獲取文件格式
    conseq.Type = document.Find(".mainRight-file").Text()
    // 獲取元數(shù)據(jù)
    context := document.Find(".main-right p").Text()
    conseq.Metadata.Mode = RegexGet(context, `尺寸:(.*?)像素`)
    conseq.Metadata.Resolution = RegexGet(context, `體積:(.*? MB)`)
    conseq.Metadata.Size = RegexGet(context, `模式:([A-Z]+)`)
    conseq.Metadata.Volume = RegexGet(context, `分辨率:(\d+dpi)`)
    // 獲取作者
    conseq.Author = document.Find(".user-name").Text()
    // 獲取圖片
    document.Find("#show-area-height img").Each(func(i int, element *goquery.Selection) {
        if attribute, exists := element.Attr("src"); exists && attribute != "" {
            conseq.Images = append(conseq.Images, attribute)
        }
    })
    // 獲取tag
    document.Find(".mainRight-tagBox .fl").Each(func(i int, element *goquery.Selection) {
        conseq.Tags = append(conseq.Tags, element.Text())
    })
    bytes, _ := json.Marshal(conseq)
    log.Println(string(bytes))
}

解析邏輯完全相同姨蟋,代碼量和復(fù)雜程度相較 python版 差不多,下面我們來看一下新出現(xiàn)的 GraphQuery 是如何做的芬探。

使用GraphQuery進行解析

已知我們想要得到的數(shù)據(jù)結(jié)構(gòu)如下:

{
    title
    pictype
    number
    type
    metadata {
        size
        volume
        mode
        resolution
    }
    author
    images []
    tags []
}

GraphQuery的代碼是下面這樣的:

{
    title `xpath("/html/body/div[4]/div[1]/div/div/div[1]/text()")`
    pictype `css(".pic-type")`
    number `css(".detailBtn-down");attr("data-id")`
    type `regex("文件格式:([a-z]+)")`
    metadata `css(".main-right p")` {
        size `regex("尺寸:(.*?)像素")`
        volume `regex("體積:(.*? MB)")`
        mode `regex("模式:([A-Z]+)")`
        resolution `regex("分辨率:(\d+dpi)")`  
    }
    author `css(".user-name")`
    images `css("#show-area-height img")` [
        src `attr("src")`
    ]
    tags `css(".mainRight-tagBox .fl")` [
        tag `text()`
    ]
}

通過對比可以看出, 它只是在我們設(shè)計的數(shù)據(jù)結(jié)構(gòu)之中添加了一些由反引號包裹起來的函數(shù)哩簿。驚艷的是,它能完全還原我們上面在 PythonGolang 中的解析邏輯羡玛,而且從它的語法結(jié)構(gòu)上宗苍,更能清晰的讀出返回的數(shù)據(jù)結(jié)構(gòu)薄榛。這段 GraphQuery 的執(zhí)行結(jié)果如下:

{
    "data": {
        "author": "Ice bear",
        "images": [
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a0", 
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a1024", 
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a2048", 
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a3072"
        ],
        "metadata": {
            "mode": "RGB",
            "resolution": "200dpi",
            "size": "4724×6299",
            "volume": "196.886 MB"
        },
        "number": "32504070",
        "pictype": "原創(chuàng)",
        "tags": ["大俠", "海報", "黑白", "金庸", "水墨", "武俠", "中國風(fēng)"],
        "title": "大俠海報金庸武俠水墨中國風(fēng)黑白",
        "type": "psd"
    },
    "error": "",
    "timecost": 10997800
}

GraphQuery 是一個文本查詢語言敞恋,它不依賴于任何后端語言谋右,可以被任何后端語言調(diào)用,一段 GraphQuery 查詢語句啸蜜,在任何語言中可以得到相同的解析結(jié)果。
它內(nèi)置了 xpath選擇器衬横,css選擇器终蒂,jsonpath 選擇器和 正則表達式 ,以及足量的文本處理函數(shù)悉尾,結(jié)構(gòu)清晰易讀挫酿,能夠保證 數(shù)據(jù)結(jié)構(gòu)解析代碼早龟、返回結(jié)果 結(jié)構(gòu)的一致性。

項目地址: github.com/storyicon/graphquery

GraphQuery 的語法簡潔易懂, 即使你是第一次接觸它壹店, 也能很快的上手芝加, 它的語法設(shè)計理念之一就是 符合直覺, 我們應(yīng)該如何執(zhí)行它呢:

1. 在Golang中調(diào)用GraphQuery

golang 中将塑,你只需要首先使用 go get -u github.com/storyicon/graphquery 獲得 graphquery 并在代碼中調(diào)用即可:

package main

import (
    "log"

    "github.com/axgle/mahonia"
    "github.com/parnurzeal/gorequest"
    "github.com/storyicon/graphquery"
)

func decoderConvert(name string, body string) string {
    return mahonia.NewDecoder(name).ConvertString(body)
}

func main() {
    request := gorequest.New()
    _, body, _ := request.Get("http://www.58pic.com/newpic/32504070.html").End()
    body = decoderConvert("gbk", body)
    response := graphquery.ParseFromString(body, "{ title `xpath(\"/html/body/div[4]/div[1]/div/div/div[1]/text()\")` pictype `css(\".pic-type\")` number `css(\".detailBtn-down\");attr(\"data-id\")` type `regex(\"文件格式:([a-z]+)\")` metadata `css(\".main-right p\")` { size `regex(\"尺寸:(.*?)像素\")` volume `regex(\"體積:(.*? MB)\")` mode `regex(\"模式:([A-Z]+)\")` resolution `regex(\"分辨率:(\\d+dpi)\")` } author `css(\".user-name\")` images `css(\"#show-area-height img\")` [ src `attr(\"src\")` ] tags `css(\".mainRight-tagBox .fl\")` [ tag `text()` ] }")
    log.Println(response)
}

我們的 GraphQuery 表達式以 單行 的形式蝌麸, 作為函數(shù) graphquery.ParseFromString 的第二個參數(shù)傳入,得到的結(jié)果與預(yù)期完全相同来吩。

2. 在Python中調(diào)用GraphQuery

Python 等其他后端語言中蔽莱,調(diào)用 GraphQuery 需要首先啟動其服務(wù)盗冷,服務(wù)已經(jīng)為 windows历葛、maclinux 編譯好,到 GraphQuery-http 中下載即可恤溶。
在解壓并啟動服務(wù)后,我們就可以愉快的使用 GraphQuery 在任何后端語言中對任何文檔以圖形的方式進行解析了鸠天。Python調(diào)用的示例代碼如下:

import requests

def GraphQuery(document, expr):
    response = requests.post("http://127.0.0.1:8559", data={
        "document": document,
        "expression": expr,
    })
    return response.text

response = requests.get("http://www.58pic.com/newpic/32504070.html")
conseq = GraphQuery(response.text, r"""
    {
        title `xpath("/html/body/div[4]/div[1]/div/div/div[1]/text()")`
        pictype `css(".pic-type")`
        number `css(".detailBtn-down");attr("data-id")`
        type `regex("文件格式:([a-z]+)")`
        metadata `css(".main-right p")` {
            size `regex("尺寸:(.*?)像素")`
            volume `regex("體積:(.*? MB)")`
            mode `regex("模式:([A-Z]+)")`
            resolution `regex("分辨率:(\d+dpi)")`  
        }
        author `css(".user-name")`
        images `css("#show-area-height img")` [
            src `attr("src")`
        ]
        tags `css(".mainRight-tagBox .fl")` [
            tag `text()`
        ]
    }
""")
print(conseq)

輸出結(jié)果為:

{
    "data": {
        "author": "Ice bear",
        "images": [
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a0", 
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a1024", 
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a2048", 
            "http://pic.qiantucdn.com/58pic/32/50/40/70d58PICZfkRTfbnM2UVe_PIC2018.jpg!/fw/1024/watermark/url/L2ltYWdlcy93YXRlcm1hcmsvZGF0dS5wbmc=/repeat/true/crop/0x1024a0a3072"
        ],
        "metadata": {
            "mode": "RGB",
            "resolution": "200dpi",
            "size": "4724×6299",
            "volume": "196.886 MB"
        },
        "number": "32504070",
        "pictype": "原創(chuàng)",
        "tags": ["大俠", "海報", "黑白", "金庸", "水墨", "武俠", "中國風(fēng)"],
        "title": "大俠海報金庸武俠水墨中國風(fēng)黑白",
        "type": "psd"
    },
    "error": "",
    "timecost": 10997800
}

三稠集、后記

復(fù)雜的解析邏輯帶來的不僅僅是代碼可讀性的問題饥瓷,在代碼的維護和移植上也會造成很大的困擾,不同的語言和不同的庫也為代碼的解析結(jié)果造成了差異晦鞋,GraphQuery 是一個全新的開源項目棺克,它的主旨就是讓開發(fā)者從這些重復(fù)繁瑣的解析邏輯中解脫出來,寫出高可讀性娜谊、高可移植性、高可維護性的代碼湾趾。歡迎實踐、持續(xù)關(guān)注與代碼貢獻撑帖,一起見證 GraphQuery 與開源社區(qū)的發(fā)展澳眷!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钳踊,一起剝皮案震驚了整個濱河市衷敌,隨后出現(xiàn)的幾起案子缴罗,更是在濱河造成了極大的恐慌祭埂,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌界,死亡現(xiàn)場離奇詭異泰演,居然都是意外死亡,警方通過查閱死者的電腦和手機睦焕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門垃喊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人本谜,你說我怎么就攤上這事◇猿桑” “怎么了眷茁?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長培遵。 經(jīng)常有香客問我,道長籽腕,這世上最難降的妖魔是什么纸俭? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮揍很,結(jié)果婚禮上万伤,老公的妹妹穿的比我還像新娘敌买。我一直安慰自己,他們只是感情好虹钮,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布膘融。 她就那樣靜靜地躺著,像睡著了一般托启。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拐迁,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天疗绣,我揣著相機與錄音,去河邊找鬼多矮。 笑死,一個胖子當著我的面吹牛塔逃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伏蚊,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼格粪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帐萎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赁项,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后肤舞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年囤耳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片德玫。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡椎麦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琴儿,到底是詐尸還是另有隱情,我是刑警寧澤嘁捷,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布雄嚣,位于F島的核電站,受9級特大地震影響缓升,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骇吭,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一封锉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧成福,春花似錦、人聲如沸奴艾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至忽冻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僧诚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工旗扑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慈省,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓袱衷,卻偏偏與公主長得像放闺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怖侦,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容