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,再结合刚才看到的原网页,可以猜出这分别代表了表格中的奇数行和偶数行,他们的背景色不一样的。

信息表格.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。
or price=9.80 or price=9.70 如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。
and price>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翻译成汉字,大概是这样的:


JSON Viewer处理的效果

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,893评论 2 89
  • scrapy学习笔记(有示例版) 我的博客 scrapy学习笔记1.使用scrapy1.1创建工程1.2创建爬虫模...
    陈思煜阅读 12,653评论 4 46
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 那篇暗恋那些事的推文,精彩的地方还是在评论。 “为了偶遇你绕遍校园”。我会为了偶遇一些想看见的人故意下课出去逛逛或...
    去社阅读 303评论 2 0