Python、Golang和GraphQuery在爬虫领域的差异如何体现?
- 内容介绍
- 文章标签
- 相关推荐
本文共计3384个文字,预计阅读时间需要14分钟。
本脚本将使用Python、Golang以及GraphQuery来解析某网站的素材详情页面,并提取页面特征。页面具有清晰的数据库结构,但DOM结构不规范,无法通过单一方式提取。
本文将分别使用Python,Golang以及GraphQuery来解析某网站的素材详情页面,这个页面的特色是具有清晰的数据结构,但是DOM结构不够规范,无法通过单独的选择器定位页面元素,对页面的解析造成了一些曲折。通过这个页面的解析过程,深入浅出的了解爬虫的解析思想与这些语言之间的异同。
一、前言
在前言中,为了防止在后面的章节产生不必要的困扰,我们将会首先了解一些基本的编程理念。
1. 语义化的DOM结构
这里我们讲的语义化的DOM结构,不仅仅包括语义化的html标签,也包括了语义化的选择器,在前端开发中应该注意的是,所有的动态文本都应该有单独的 html 标签包裹,并最好赋予其语义化的class属性或id属性,这在版本功能的迭代中,对前端和后端的开发都是大有裨益的,比如下面的HTML代码:
这就是不够语义化的前端代码,32504070,RGB,16.659 MB,72dpi这些值都是动态属性, 会跟随编号的改变而改变,在规范的开发中,应该将这些动态变化的属性,分别用<span>这类行内标签包裹起来,并赋予其一定的语义化选择器,在上面的HTML结构中大致可以推测出这是后端直接使用 foreach 渲染出的页面,这是不符合前后端分离的思想的,如果有一天他们决定使用jsonp或Ajax渲染这些属性, 由前端进行渲染,工作量无疑会上一个层次。语义化的DOM结构更倾向于下面这样:
也可以将property-mode直接作为span的class属性,这样这些属性无论是后端渲染,还是前端动态渲染都减轻了产品迭代产生的负担。
2. 稳定的解析代码
在语义化的DOM结构之后,我们来谈谈稳定的解析代码, 对于下面的DOM结构:
如果我们想要提取模式信息,当然可以采取下面的步骤:
选取class属性中包含main-right的div
选取这个div中第二个p元素,取出其包含的文本
删除文本中的模式:, 得到模式为RGB
虽然成功获取到了想要的结果,但是这样的解析方法,我们认为它是不稳定的,这个不稳定是指在其祖先元素、兄弟元素等自身以外的元素节点发生一定程度的结构改变时,导致解析错误或失败的情况, 比如如果有一天在模式所在的节点之前增加了一个尺寸的属性:
那么我们之前的解析将会发生错误(什么?你觉得不可能发生这样的变动?请对比Page1和Page2)。
那我们应该如何写出更稳定的解析代码呢,对于上面的DOM结构,我们可以有下面几种思路:
思路一: 遍历class属性为main-rightStage的p节点,依次判断节点的文本是否以模式开头, 如果是, 取出其:后的内容,缺点是逻辑太多,不易维护且降低了代码可读性。
思路二: 使用正则表达式模式:([A-Z]+)进行匹配,缺点是使用不当可能造成效率问题。
思路三: 使用 CSS选择器中的contains方法,比如.main-rightStage:contains(模式), 就可以选取文本中包含模式,且class属性中包含main-rightStage的节点了。但缺点是不同语言和不同库对这种语法的支持程度各有不同,缺乏兼容性。
使用哪种方法,仁者见仁智者见智,不同的解析思路带来的解析的稳定性、代码的复杂程度、运行效率和兼容性都是不同的, 开发者需要从各种因素中进行权衡, 来写出最优秀的解析代码。
二、进行页面的解析
在进行页面数据的抽取之前,首先要做的是明确我们需要哪些数据、页面上提供了哪些数据,然后设计出我们需要的数据结构。首先打开待解析页面, 由于其最上方的浏览量、收藏量、下载量等数据是动态加载的, 在我们的演示中暂时不需要,而这个页面右边的尺寸、模式等数据,通过上面Page1和Page2的对比,可以得知这些属性是不一定存在的,因此将它们一起归到metainfo中。因此我们需要获得的数据如下图所示:
由此我们可以很快设计出我们的数据结构:
其中size、volume、mode、resolution由于可能不存在,因此归入到了metadata下,images是一个图片地址的数组,tags是标签数组,在确定了要提取的数据结构,就可以开始进行解析。
使用Python进行页面的解析
Python库的数量非常庞大,有很多优秀的库可以帮助到我们,在使用Python进行页面的解析时,我们通常用到下面这些库:
提供正则表达式支持的re库
提供CSS选择器支持的pyquery和beautifulsoup4
提供Xpath支持的lxml库
提供JSON PATH支持的jsonpath_rw库
这些库在Python 3下获得支持的,可以通过pip install进行安装。
由于CSS选择器的语法比Xpath语法要更加简洁,而在方法的调用上,pyquery比beautifulsoup4要更加方便,因此在 2 和 3 之间我们选择了pyquery。
下面我们会以title和type属性的获取作为例子进行讲解, 其他节点的获取是同理的。首先我们先使用requests库下载这个页面的源文件:
下面使用Python进行的解析都将依次为前提进行。
1. 获取title节点
打开待解析页面,在标题上右键, 点击查看元素,可以看到它的DOM结构如下:
这时我们注意到, 我们想要提取出的标题文本大侠海报金庸武侠水墨中国风黑白,并没有被html标签包裹,这是不符合我们上面提到的语义化的dom结构的。同时,使用CSS选择器,也是无法直接选取到这个文本节点的(可以使用Xpath直接选取到,本文略)。对于这样的节点,我们可以有下面两种思路:
思路一: 先选取其父元素节点, 获取其 HTML 内容,使用正则表达式, 匹配在</div>和<p之间的文本。
思路二: 先选取其父元素节点,然后删除文本节点之外的其他节点,再直接通过获取父元素节点的文本,得到想要的标题文本。
我们采取思路二,写出下面的Python代码:
输出结果与我们期望的相同, 为大侠海报金庸武侠水墨中国风黑白。
2. 获取size节点
在尺寸上右键查看元素,可以看到下图所示的DOM结构:
我们发现这些节点不具有语义化的选择器,并且这些属性不一定都存在(详见Page1和Page2的对比)。在稳定的解析代码中我们也讲到了对于这种结构的文档可以采取的几种思路,这里我们采用正则解析的方法:
由于获取size、volume、mode、resolution这些属性,都可以采取类似的方法,因此我们可以归结出一个正则提取的函数:
因此,在获取size节点时,我们的代码就可以精简为:
3. 完整的Python代码
到这里,我们解析页面可能遇到的问题就已经解决了大半,整个Python代码如下:
使用Golang进行页面的解析
在Golang中解析html和xml文档, 常用到的库有以下几种:
提供正则表达式支持的regexp库
提供CSS选择器支持的github.com/PuerkitoBio/goquery
提供Xpath支持的gopkg.in/xmlpath.v2库
提供JSON PATH支持的github.com/tidwall/gjson库
这些库,你都可以通过go get -u来获取,由于在上面的Python解析中我们已经整理出了解析逻辑,在Golang中只需要复现即可,与Python不同的是,我们最好先为我们的数据结构定义一个 struct,像下面这样:
同时,由于我们的待解析页面是非主流的gbk编码,所以在下载下来文档之后,需要手动将utf-8的编码转换为gbk的编码,这个过程虽然不在解析的范畴之内,但是也是必须要做的步骤之一, 我们使用了github.com/axgle/mahonia这个库进行编码的转换,并整理出了编码转换的函数decoderConvert:
因此, 最终的golang代码应该是下面这样的:
解析逻辑完全相同,代码量和复杂程度相较python版差不多,下面我们来看一下新出现的GraphQuery是如何做的。
使用GraphQuery进行解析
已知我们想要得到的数据结构如下:
GraphQuery的代码是下面这样的:
通过对比可以看出, 它只是在我们设计的数据结构之中添加了一些由反引号包裹起来的函数。惊艳的是,它能完全还原我们上面在Python和Golang中的解析逻辑,而且从它的语法结构上,更能清晰的读出返回的数据结构。这段GraphQuery的执行结果如下:
GraphQuery是一个文本查询语言,它不依赖于任何后端语言,可以被任何后端语言调用,一段GraphQuery查询语句,在任何语言中可以得到相同的解析结果。
它内置了xpath选择器,css选择器,jsonpath选择器和正则表达式,以及足量的文本处理函数,结构清晰易读,能够保证数据结构、解析代码、返回结果结构的一致性。
项目地址:github.com/storyicon/graphquery
GraphQuery的语法简洁易懂, 即使你是第一次接触它, 也能很快的上手, 它的语法设计理念之一就是符合直觉, 我们应该如何执行它呢:
1. 在Golang中调用GraphQuery
在golang中,你只需要首先使用go get -u github.com/storyicon/graphquery获得GraphQuery并在代码中调用即可:
我们的GraphQuery表达式以单行的形式, 作为函数graphquery.ParseFromString的第二个参数传入,得到的结果与预期完全相同。
2. 在Python中调用GraphQuery
在Python等其他后端语言中,调用GraphQuery需要首先启动其服务,服务已经为windows、mac和linux编译好,到github.com/storyicon/graphquery-http/releases中下载即可。
在解压并启动服务后,我们就可以愉快的使用GraphQuery在任何后端语言中对任何文档以图形的方式进行解析了。Python调用的示例代码如下:
输出结果为:
三、后记
复杂的解析逻辑带来的不仅仅是代码可读性的问题,在代码的维护和移植上也会造成很大的困扰,不同的语言和不同的库也为代码的解析结果造成了差异,GraphQuery是一个全新的开源项目,它的主旨就是让开发者从这些重复繁琐的解析逻辑中解脱出来,写出高可读性、高可移植性、高可维护性的代码。欢迎实践、持续关注与代码贡献,一起见证GraphQuery与开源社区的发展!
本文共计3384个文字,预计阅读时间需要14分钟。
本脚本将使用Python、Golang以及GraphQuery来解析某网站的素材详情页面,并提取页面特征。页面具有清晰的数据库结构,但DOM结构不规范,无法通过单一方式提取。
本文将分别使用Python,Golang以及GraphQuery来解析某网站的素材详情页面,这个页面的特色是具有清晰的数据结构,但是DOM结构不够规范,无法通过单独的选择器定位页面元素,对页面的解析造成了一些曲折。通过这个页面的解析过程,深入浅出的了解爬虫的解析思想与这些语言之间的异同。
一、前言
在前言中,为了防止在后面的章节产生不必要的困扰,我们将会首先了解一些基本的编程理念。
1. 语义化的DOM结构
这里我们讲的语义化的DOM结构,不仅仅包括语义化的html标签,也包括了语义化的选择器,在前端开发中应该注意的是,所有的动态文本都应该有单独的 html 标签包裹,并最好赋予其语义化的class属性或id属性,这在版本功能的迭代中,对前端和后端的开发都是大有裨益的,比如下面的HTML代码:
这就是不够语义化的前端代码,32504070,RGB,16.659 MB,72dpi这些值都是动态属性, 会跟随编号的改变而改变,在规范的开发中,应该将这些动态变化的属性,分别用<span>这类行内标签包裹起来,并赋予其一定的语义化选择器,在上面的HTML结构中大致可以推测出这是后端直接使用 foreach 渲染出的页面,这是不符合前后端分离的思想的,如果有一天他们决定使用jsonp或Ajax渲染这些属性, 由前端进行渲染,工作量无疑会上一个层次。语义化的DOM结构更倾向于下面这样:
也可以将property-mode直接作为span的class属性,这样这些属性无论是后端渲染,还是前端动态渲染都减轻了产品迭代产生的负担。
2. 稳定的解析代码
在语义化的DOM结构之后,我们来谈谈稳定的解析代码, 对于下面的DOM结构:
如果我们想要提取模式信息,当然可以采取下面的步骤:
选取class属性中包含main-right的div
选取这个div中第二个p元素,取出其包含的文本
删除文本中的模式:, 得到模式为RGB
虽然成功获取到了想要的结果,但是这样的解析方法,我们认为它是不稳定的,这个不稳定是指在其祖先元素、兄弟元素等自身以外的元素节点发生一定程度的结构改变时,导致解析错误或失败的情况, 比如如果有一天在模式所在的节点之前增加了一个尺寸的属性:
那么我们之前的解析将会发生错误(什么?你觉得不可能发生这样的变动?请对比Page1和Page2)。
那我们应该如何写出更稳定的解析代码呢,对于上面的DOM结构,我们可以有下面几种思路:
思路一: 遍历class属性为main-rightStage的p节点,依次判断节点的文本是否以模式开头, 如果是, 取出其:后的内容,缺点是逻辑太多,不易维护且降低了代码可读性。
思路二: 使用正则表达式模式:([A-Z]+)进行匹配,缺点是使用不当可能造成效率问题。
思路三: 使用 CSS选择器中的contains方法,比如.main-rightStage:contains(模式), 就可以选取文本中包含模式,且class属性中包含main-rightStage的节点了。但缺点是不同语言和不同库对这种语法的支持程度各有不同,缺乏兼容性。
使用哪种方法,仁者见仁智者见智,不同的解析思路带来的解析的稳定性、代码的复杂程度、运行效率和兼容性都是不同的, 开发者需要从各种因素中进行权衡, 来写出最优秀的解析代码。
二、进行页面的解析
在进行页面数据的抽取之前,首先要做的是明确我们需要哪些数据、页面上提供了哪些数据,然后设计出我们需要的数据结构。首先打开待解析页面, 由于其最上方的浏览量、收藏量、下载量等数据是动态加载的, 在我们的演示中暂时不需要,而这个页面右边的尺寸、模式等数据,通过上面Page1和Page2的对比,可以得知这些属性是不一定存在的,因此将它们一起归到metainfo中。因此我们需要获得的数据如下图所示:
由此我们可以很快设计出我们的数据结构:
其中size、volume、mode、resolution由于可能不存在,因此归入到了metadata下,images是一个图片地址的数组,tags是标签数组,在确定了要提取的数据结构,就可以开始进行解析。
使用Python进行页面的解析
Python库的数量非常庞大,有很多优秀的库可以帮助到我们,在使用Python进行页面的解析时,我们通常用到下面这些库:
提供正则表达式支持的re库
提供CSS选择器支持的pyquery和beautifulsoup4
提供Xpath支持的lxml库
提供JSON PATH支持的jsonpath_rw库
这些库在Python 3下获得支持的,可以通过pip install进行安装。
由于CSS选择器的语法比Xpath语法要更加简洁,而在方法的调用上,pyquery比beautifulsoup4要更加方便,因此在 2 和 3 之间我们选择了pyquery。
下面我们会以title和type属性的获取作为例子进行讲解, 其他节点的获取是同理的。首先我们先使用requests库下载这个页面的源文件:
下面使用Python进行的解析都将依次为前提进行。
1. 获取title节点
打开待解析页面,在标题上右键, 点击查看元素,可以看到它的DOM结构如下:
这时我们注意到, 我们想要提取出的标题文本大侠海报金庸武侠水墨中国风黑白,并没有被html标签包裹,这是不符合我们上面提到的语义化的dom结构的。同时,使用CSS选择器,也是无法直接选取到这个文本节点的(可以使用Xpath直接选取到,本文略)。对于这样的节点,我们可以有下面两种思路:
思路一: 先选取其父元素节点, 获取其 HTML 内容,使用正则表达式, 匹配在</div>和<p之间的文本。
思路二: 先选取其父元素节点,然后删除文本节点之外的其他节点,再直接通过获取父元素节点的文本,得到想要的标题文本。
我们采取思路二,写出下面的Python代码:
输出结果与我们期望的相同, 为大侠海报金庸武侠水墨中国风黑白。
2. 获取size节点
在尺寸上右键查看元素,可以看到下图所示的DOM结构:
我们发现这些节点不具有语义化的选择器,并且这些属性不一定都存在(详见Page1和Page2的对比)。在稳定的解析代码中我们也讲到了对于这种结构的文档可以采取的几种思路,这里我们采用正则解析的方法:
由于获取size、volume、mode、resolution这些属性,都可以采取类似的方法,因此我们可以归结出一个正则提取的函数:
因此,在获取size节点时,我们的代码就可以精简为:
3. 完整的Python代码
到这里,我们解析页面可能遇到的问题就已经解决了大半,整个Python代码如下:
使用Golang进行页面的解析
在Golang中解析html和xml文档, 常用到的库有以下几种:
提供正则表达式支持的regexp库
提供CSS选择器支持的github.com/PuerkitoBio/goquery
提供Xpath支持的gopkg.in/xmlpath.v2库
提供JSON PATH支持的github.com/tidwall/gjson库
这些库,你都可以通过go get -u来获取,由于在上面的Python解析中我们已经整理出了解析逻辑,在Golang中只需要复现即可,与Python不同的是,我们最好先为我们的数据结构定义一个 struct,像下面这样:
同时,由于我们的待解析页面是非主流的gbk编码,所以在下载下来文档之后,需要手动将utf-8的编码转换为gbk的编码,这个过程虽然不在解析的范畴之内,但是也是必须要做的步骤之一, 我们使用了github.com/axgle/mahonia这个库进行编码的转换,并整理出了编码转换的函数decoderConvert:
因此, 最终的golang代码应该是下面这样的:
解析逻辑完全相同,代码量和复杂程度相较python版差不多,下面我们来看一下新出现的GraphQuery是如何做的。
使用GraphQuery进行解析
已知我们想要得到的数据结构如下:
GraphQuery的代码是下面这样的:
通过对比可以看出, 它只是在我们设计的数据结构之中添加了一些由反引号包裹起来的函数。惊艳的是,它能完全还原我们上面在Python和Golang中的解析逻辑,而且从它的语法结构上,更能清晰的读出返回的数据结构。这段GraphQuery的执行结果如下:
GraphQuery是一个文本查询语言,它不依赖于任何后端语言,可以被任何后端语言调用,一段GraphQuery查询语句,在任何语言中可以得到相同的解析结果。
它内置了xpath选择器,css选择器,jsonpath选择器和正则表达式,以及足量的文本处理函数,结构清晰易读,能够保证数据结构、解析代码、返回结果结构的一致性。
项目地址:github.com/storyicon/graphquery
GraphQuery的语法简洁易懂, 即使你是第一次接触它, 也能很快的上手, 它的语法设计理念之一就是符合直觉, 我们应该如何执行它呢:
1. 在Golang中调用GraphQuery
在golang中,你只需要首先使用go get -u github.com/storyicon/graphquery获得GraphQuery并在代码中调用即可:
我们的GraphQuery表达式以单行的形式, 作为函数graphquery.ParseFromString的第二个参数传入,得到的结果与预期完全相同。
2. 在Python中调用GraphQuery
在Python等其他后端语言中,调用GraphQuery需要首先启动其服务,服务已经为windows、mac和linux编译好,到github.com/storyicon/graphquery-http/releases中下载即可。
在解压并启动服务后,我们就可以愉快的使用GraphQuery在任何后端语言中对任何文档以图形的方式进行解析了。Python调用的示例代码如下:
输出结果为:
三、后记
复杂的解析逻辑带来的不仅仅是代码可读性的问题,在代码的维护和移植上也会造成很大的困扰,不同的语言和不同的库也为代码的解析结果造成了差异,GraphQuery是一个全新的开源项目,它的主旨就是让开发者从这些重复繁琐的解析逻辑中解脱出来,写出高可读性、高可移植性、高可维护性的代码。欢迎实践、持续关注与代码贡献,一起见证GraphQuery与开源社区的发展!

