Scrapy学习笔记(二)提取数据

在上一节里面,我定义了结构化字段Item,然而并没有用到它。
所以,为了能够将有用的信息整理到Item中去,我们需要了解一下提取页面有效信息的办法。

  • 这里要用到一个小工具——Selectors(选择器)

Selector是Scrapy内置的功能,它支持使用XPathCSS Selector两种表达式来对信息进行搜索。
如果有同学用过正则表达式的话,对以上两种语言应该有些感性认识了。不过比起正则,也许BeautifulSoup插件中的find Tag与Selector更加相似,它们都可以很方便地对提取出html中的标签。(其实写这段我有点不确定的,因为据说XPath应该是一门在 XML 文档中查找信息的语言,XML和HTML之间的差异,我理解得不够深刻。)
这里附上XPath的教程:http://www.w3school.com.cn/xpath/index.asp

Step1:网页源代码分析

现在,我们就打开上一节中我们保存下来的tencent.txt,我从中选取了一小段我们比较感兴趣的数据。

<tr class="odd">
            <td class="l square"><a target="_blank" href="position_detail.php?id=42477&keywords=&tid=0&lid=0">SNG16-腾讯音乐多媒体AI研究员(深圳)</a></td>
        <td>技术类</td>
        <td>3</td>
        <td>深圳</td>
        <td>2018-07-14</td>
</tr>
<tr class="even">
        <td class="l square"><a target="_blank" href="position_detail.php?id=42471&keywords=&tid=0&lid=0">22989-专有云网络运维工程师(北京/上海/深圳)</a><span class="hot">&nbsp;</span></td>
        <td>技术类</td>
        <td>2</td>
        <td>深圳</td>
        <td>2018-07-14</td>
</tr>
<tr class="odd">
        <td class="l square"><a target="_blank" href="position_detail.php?id=42472&keywords=&tid=0&lid=0">22989-专有云数据库运维工程师(北京/上海/深圳)</a><span class="hot">&nbsp;</span></td>
        <td>技术类</td>
        <td>2</td>
        <td>深圳</td>
        <td>2018-07-14</td>
        </tr>

初看杂乱的信息中也是有不少规律的嘛。
看第一行中的odd和下面几行中的even,再结合刚才看到的原网页,可以猜出这分别代表了表格中的奇数行和偶数行,他们的背景色不一样的。

《Scrapy学习笔记(二)提取数据》 信息表格.jpg

经过浏览器的渲染,摘录的这段呈现在我们眼中,就是表格的最后三行了。

现在回头看一下上节笔记中定义的Item信息,就可以一一对应上了,以最后一条为例:

  • name = 22989-专有云数据库运维工程师(北京/上海/深圳)
  • detailLink = position_detail.php?id=42472&keywords=&tid=0&lid=0
  • catalog = 技术类
  • recruitNumber = 2
  • workLocation = 深圳
  • publishTime = 2018-07-14

固然我们可以手动从中挑出想要的信息来,但最终的目的依然是让程序帮我们完成一切,我们对html源代码的关注,是为了找出有用信息的特征。

  • 可以看出一对<tr>包括了一条招聘信息,而<td>括起了一条信息中的各个元素。

那是不是如果按顺序提取出(tr[1],tr[2],……)再索引到(td[1],td[2],……)就可以对应上我们想要的信息了呢?
没错,不过在XPath里有更简单形式的语法能够帮我们找到他们。

Step2:XPath语法

表达式描述
nodename选取nodename节点的所有子节点。
.选取当前节点。
..选取当前节点的父节点。
@选取属性。
[ ]根据中括号的内容作筛选
*通配符:选择所有元素节点

还有一个表达式符号我想单独拿出来说,就是/,大家知道/在系统里用作文件夹层次的分隔,同样地,/应该配合以上表达式食用。单独的/表示根节点位置。

除了表达式,XPath还有一类构成要素:运算符。运算符可以连接表达式,以更方便地完成任务。我列出了感觉会常用的,想了解更多可以查看这个链接:http://www.w3school.com.cn/xpath/xpath_operators.asp

运算符描述实例返回值
|计算两个节点集//book | //cd返回所有拥有 book 和 cd 元素的节点集
>=大于或等于price>=9.80如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。
orprice=9.80 or price=9.70如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
andprice>9.00 and price<9.90如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。
<tr class="odd">
            <td class="l square"><a target="_blank" href="position_detail.php?id=42477&keywords=&tid=0&lid=0">SNG16-腾讯音乐多媒体AI研究员(深圳)</a></td>
        <td>技术类</td>
        <td>3</td>
        <td>深圳</td>
        <td>2018-07-14</td>
</tr>

表格中信息的特征很好辨认,奇数行的内容一定具有属性 “odd”,我们可以编写表达式把奇数行的找出来:

'//*[@class="odd"]'
  • 表达式的解释
    刚才提到/可以表示根结点,那么//又是什么意思呢,它表示从当前节点开始递归下降,此路径运算符出现在模式开头时,表示应从根节点递归下降。
    所以代码的意思就是:从根节点开始寻找所有符合要求的节点,要求是该行属性为奇数行
    可是一般来说我们关注的对象可不仅仅是奇数行,还有偶数行呢。所以啊用上面提到的节点集符号|修改一下表达式就可以了:
//*[@class="even"] | //*[@class="odd"]

这样,我们就找到了一条招聘信息的节点,为了能够分别保存职位名称、类型、工作地点等信息,我们需要对上一步找到的节点做进一步处理:

'./td[1]/a/text()'#当前节点中,第1个td节点里,a节点内的文字:name
'./td[1]/a/@href'#当前节点中,第1个td节点里,a节点内,herf属性的内容:detailLink
'./td[2]/text()'#当前节点中,第2个td节点里,a节点内的文字:catalog
'./td[3]/text()'#当前节点中,第3个td节点里,a节点内的文字:recruitNumber
'./td[4]/text()'#当前节点中,第4个td节点里,a节点内的文字:workLocation
'./td[5]/text()'#当前节点中,第5个td节点里,a节点内的文字:publishTime

一一对应的关系找到了,恭喜我们,终于理解XPath的初级用法了。接下来要把命令交给爬虫执行,就需要把它放在tencent.py的parse函数中。

Step3:编写爬取代码

打开tencent.py,在开头添加上我们对items.py里定义好的结构化字段的引用,然后修改整个文档的代码如下:

import scrapy
from tutorial.items import RecruitItem

class RecruitSpider(scrapy.Spider):
    name = "tencent"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]

    def parse(self, response):
        for sel in response.xpath('//*[@class="even"] | //*[@class="odd"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog = sel.xpath('./td[2]/text()').extract()[0]
            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]

            item = RecruitItem()
            item['name'] = name.encode('utf-8')
            item['detailLink'] = detailLink.encode('utf-8')
            item['catalog'] = catalog.encode('utf-8')
            item['recruitNumber'] = recruitNumber.encode('utf-8')
            item['workLocation'] = workLocation.encode('utf-8')
            item['publishTime'] = publishTime.encode('utf-8')

            yield item

代码执行部分结构有3层:

  1. for sel in response.xpath('//*[@class="even"] | //*[@class="odd"]'):
    首先找出页面中所有符合表格奇行和偶行特征的节点,然后用sel这个临时变量去遍历他们;
  2. name = sel.xpath('./td[1]/a/text()').extract()[0]
    用XPath表达式去寻找sel中符合要求的元素,分别存入临时的字段里。
    至于为什么语法是这样的,可以参考:
    xpath().extract()和xpath().extract()[0] 的区别? – 知乎用户的回答 – 知乎
    https://www.zhihu.com/question/63370553/answer/247633004
  3. item['name'] = name.encode('utf-8')
    将临时字段里的内容转码成UTF-8再存入item的各字段中。yield类似于C语言的return。

至此代码部分就编写完成啦~

Step4:爬取信息

来来,再度回到\tutorial文件夹下,运行终端,输入:

scrapy crawl tencent -o items.json

即在当前目录下生成了items.json。
JSON语言能够方便地被读取,我理解为轻量的数据库。
我是第一次处理json格式的语言,所以在用NPP记事本打开文档的时候,望着格式一团糟的json头晕了。不过为了降低学习成本,暂时还没有开数据库的坑。那就找一个能够方便地把UTF-8代码显示成汉字的工具吧。
我是选择了NPP的插件JSON Viewer,可以对JSON可视化排版,也能把UTF-8翻译成汉字,大概是这样的:

《Scrapy学习笔记(二)提取数据》 JSON Viewer处理的效果

嗯……就是这样。
但是这个界面……求一款颜值高的本地工具,谢谢。

    原文作者:依吖
    原文地址: https://www.jianshu.com/p/ccdd0e9febf5
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞