[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xp71y0qa-1572485093461)(assets/scrapy_all-1560111729968.png)]
Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spider来处理,Spider(爬虫):它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spider中间通信的功能组件(比如进入Spider的Responses;和从Spider出去的Requests)代码写好,程序开始运行…
引擎:Hi!Spider, 你要处理哪一个网站?Spider:老大要我处理xxxx.com。引擎:你把第一个需要处理的URL给我吧。Spider:给你,第一个URL是xxxxxxx.com。引擎:Hi!调度器,我这有request请求你帮我排序入队一下。调度器:好的,正在处理你等一下。引擎:Hi!调度器,把你处理好的request请求给我。调度器:给你,这是我处理好的request引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿responses默认是交给def parse()这个函数处理的)Spider:(处理完毕数据之后对于需要跟进的URL),Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。引擎:Hi !管道 我这儿有个item你帮我处理一下!调度器!这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。管道``调度器:好的,现在就做!注意:只有当调度器没有request需要处理时,整个程序才会停止。(对于下载失败的URL,Scrapy也会重新下载。)
制作 Scrapy 爬虫 一共需要4步:
新建项目 (scrapy startproject xxx):新建一个新的爬虫项目明确目标 (编写items.py):明确你想要抓取的目标制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页存储内容 (pipelines.py):设计管道存储爬取内容————————————————————————————————————————————————————————
Scrapy框架官方网址:http://doc.scrapy.org/en/latest
Scrapy中文维护站点:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html
安装后,只要在命令终端输入 scrapy,提示类似以下结果,代表已经安装成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bviQvzDn-1572485093462)(assets/scrapy01-1560111747841.PNG)]
具体Scrapy安装流程参考:http://doc.scrapy.org/en/latest/intro/install.html#intro-install-platform-notes 里面有各个平台的安装方法
——————————————————————————————————————————————————
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0H6839z7-1572485093462)(assets/scrapy02-1560111759238.PNG)]
下面来简单介绍一下各个主要文件的作用:
scrapy.cfg :项目的配置文件
mySpider/ :项目的Python模块,将会从这里引用代码
mySpider/items.py :项目的目标文件
mySpider/pipelines.py :项目的管道文件
mySpider/settings.py :项目的设置文件
mySpider/spiders/ :存储爬虫代码目录
我们打算抓取:http://movie.mtime.com/boxoffice/#world/weekend 网站里的发布时间,标题,观看人数。
打开mySpider目录下的items.pyItem 定义结构化数据字段,用来保存爬取到的数据,有点像Python中的dict,但是提供了一些额外的保护减少错误。可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field的类属性来定义一个Item(可以理解成类似于ORM的映射关系)。接下来,创建一个MyspiderItem类,和构建item模型(model)。 # items.py import scrapy class MyspiderItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() # 标题 product_little = scrapy.Field() # 类型 product_type = scrapy.Field() # 观看人数 see_count = scrapy.Field() # 点赞人数 zan_count = scrapy.Field()爬虫功能要分两步:
其实也可以由我们自行创建new_chang.py并编写上面的代码,只不过使用命令可以免去编写固定代码的麻烦
要建立一个Spider, 你必须用scrapy.Spider类创建一个子类,并确定了三个强制的属性 和 一个方法。
name = "" :这个爬虫的识别名称,必须是唯一的,在不同的爬虫必须定义不同的名字。allowed_domains = [] 是搜索的域名范围,也就是爬虫的约束区域,规定爬虫只爬取这个域名下的网页,不存在的URL会被忽略。start_urls = () :爬取的URL元祖/列表。爬虫从这里开始抓取数据,所以,第一次下载的数据将会从这些urls开始。其他子URL将会从这些起始URL中继承性生成。parse(self, response) :解析的方法,每个初始URL完成下载后将被调用,调用的时候传入从每一个URL传回的Response对象来作为唯一参数,主要作用如下: 负责解析返回的网页数据(response.body),提取结构化数据(生成item)生成需要下一页的URL请求。将start_urls的值修改为需要爬取的第一个url
start_urls = ("http://www.baidu.cn/channel/teacher.shtml",)修改parse()方法
def parse(self, response): with open("new.html", "w") as f: f.write(response.text)然后运行一下看看,在mySpider目录下执行:
scrapy crawl new_chang是的,就是 new_chang,看上面代码,它是NewChangSpider 类的 name 属性,也就是使用 scrapy genspider命令的爬虫名。
一个Scrapy爬虫项目里,可以存在多个爬虫。各个爬虫在执行时,就是按照 name 属性来区分。
运行之后,如果打印的日志出现 [scrapy] INFO: Spider closed (finished),代表执行完成。 之后当前文件夹中就出现了一个new.html 文件,里面就是我们刚刚要爬取的网页的全部源代码信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BfdnxaOi-1572485093463)(assets/scrapy03-1560111915491.PNG)]
# 注意,Python2.x默认编码环境是ASCII,当和取回的数据编码格式不一致时,可能会造成乱码; # 我们可以指定保存内容的编码格式,一般情况下,我们可以在代码最上方添加: import sys reload(sys) sys.setdefaultencoding("utf-8") # 这三行代码是Python2.x里解决中文编码的万能钥匙,经过这么多年的吐槽后Python3学乖了,默认编码是Unicode了...[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VGWjNrmt-1572485093463)(assets/scrapy04.PNG)]
<a href="javascript:;"> <p class="fs_14 fw_600 c_b_3 line-hide-1">晴天,海边</p> </a> <div class="new-cate"> <span class="fs_12 fw_300 c_b_9">MV</span> <span class="i-icon v-center"></span> <span class="fs_12 fw_300 c_b_9"> 纪录 - 旅行 </span> </div> <div class="video-view fs_12 fw_300 c_b_9"> <span class="fw_300 icon-play-volume">1.5w</span> <span class="fw_300 c_b_9 icon-like">151</span> </div>是不是一目了然?直接上XPath开始提取数据吧。
我们之前在mySpider/items.py 里定义了一个MyspiderItem类。 这里引入进来 from ..items import MyspiderItem 然后将我们得到的数据封装到一个 MyspiderItem 对象中,可以保存每个片场的属性: # -*- coding: utf-8 -*- import scrapy from ..items import MyspiderItem class NewChangSpider(scrapy.Spider): name = 'new_chang' allowed_domains = ['xinpianchang.com'] start_urls = ['https://www.xinpianchang.com/channel/index/sort-like'] def parse(self, response): items = [] for eatch in response.xpath('//li[@class="enter-filmplay"]//div[@class="video-con"]'): # 将我们得到的数据封装到一个 `MyspiderItem` 对象 item = MyspiderItem() # extract()方法返回的都是unicode字符串 product_little = eatch.xpath('.//a/p/text()').extract() print("*" * 100) product_type = eatch.xpath('.//div[@class="new-cate"]//span/text()').extract() see_count = eatch.xpath('.//div[@class="video-view fs_12 fw_300 c_b_9"]/span/text()')[0].extract() zan_count = eatch.xpath('.//div[@class="video-view fs_12 fw_300 c_b_9"]/span/text()')[1].extract() print("see_count", zan_count) # xpath返回的是包含元素的列表 item["product_little"] = product_little item["product_type"] = product_type item["see_count"] = see_count item["zan_count"] = zan_count items.append(item) return items 我们暂时先不处理管道,后面会详细介绍。scrapy保存信息的最简单的方法主要有四种,-o 输出指定格式的文件,,命令如下:
# json格式,默认为Unicode编码 scrapy crawl new_chang -o new.json # json lines格式,默认为Unicode编码 scrapy crawl new_chang -o new.jsonl # csv 逗号表达式,可用Excel打开 scrapy crawl new_chang -o new.csv # xml格式 scrapy crawl new_chang -o new.xml思考
如果将代码改成下面形式,结果完全一样。
# -*- coding: utf-8 -*- import scrapy from ..items import MyspiderItem class NewChangSpider(scrapy.Spider): name = 'new_chang' allowed_domains = ['xinpianchang.com'] start_urls = ['https://www.xinpianchang.com/channel/index/sort-like'] def parse(self, response): items = [] # for eatch in response.xpath('//li[@class="enter-filmplay"]//div[@class="video-con"]')[:2]: # 将我们得到的数据封装到一个 `MyspiderItem` 对象 item = MyspiderItem() # extract()方法返回的都是unicode字符串 product_little = eatch.xpath('.//a/p/text()').extract() print("*" * 100) # product_type = eatch.xpath('.//div[@class="new-cate"]//span/text()').extract() see_count = eatch.xpath('.//div[@class="video-view fs_12 fw_300 c_b_9"]/span/text()')[0].extract() zan_count = eatch.xpath('.//div[@class="video-view fs_12 fw_300 c_b_9"]/span/text()')[1].extract() print("see_count", zan_count) # xpath返回的是包含元素的列表 item["product_little"] = product_little item["product_type"] = product_type item["see_count"] = see_count item["zan_count"] = zan_count yield itemScrapy终端是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPath或CSS表达式,查看他们的工作方式,方便我们爬取的网页中提取的数据。
如果安装了 IPython ,Scrapy终端将使用 IPython (替代标准Python终端)。 IPython 终端与其他相比更为强大,提供智能的自动补全,高亮输出,及其他特性。(推荐安装IPython)
进入项目的根目录,执行下列命令来启动shell:
scrapy shell "https://www.xinpianchang.com/channel/index/sort-like"[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-teTZcpnU-1572485093463)(assets/scrapy05.png)]
Scrapy Shell根据下载的页面会自动创建一些方便使用的对象,例如 Response 对象,以及 Selector 对象 (对HTML及XML内容)。
当shell载入后,将得到一个包含response数据的本地 response 变量,输入 response.body将输出response的包体,输出 response.headers 可以看到response的包头。输入 response.selector 时, 将获取到一个response 初始化的类 Selector 的对象,此时可以通过使用 response.selector.xpath()或response.selector.css() 来对 response 进行查询。Scrapy也提供了一些快捷方式, 例如 response.xpath()或response.css()同样可以生效(如之前的案例)。Scrapy Selectors 内置 XPath 和 CSS Selector 表达式机制
Selector有四个基本的方法,最常用的还是xpath:
xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表extract(): 序列化该节点为Unicode字符串并返回listcss(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表,语法同 BeautifulSoup4re(): 根据传入的正则表达式对数据进行提取,返回Unicode字符串list列表以后做数据提取的时候,可以把现在Scrapy Shell中测试,测试通过后再应用到代码中。
当然Scrapy Shell作用不仅仅如此,但是不属于我们课程重点,不做详细介绍。
官方文档:http://scrapy-chs.readthedocs.io/zh_CN/latest/topics/shell.html
——————————————————————————————————————————————————
当Item在Spider中被收集之后,它将会被传递到Item Pipeline,这些Item Pipeline组件按定义的顺序处理Item。
每个Item Pipeline都是实现了简单方法的Python类,比如决定此Item是丢弃而存储。以下是item pipeline的一些典型应用:
验证爬取的数据(检查item包含某些字段,比如说name字段)查重(并丢弃)将爬取结果保存到文件或者数据库中编写item pipeline很简单,item pipiline组件是一个独立的Python类,其中process_item()方法必须实现:
import something class SomethingPipeline(object): def __init__(self): # 可选实现,做参数初始化等 # doing something def process_item(self, item, spider): # item (Item 对象) – 被爬取的item # spider (Spider 对象) – 爬取该item的spider # 这个方法必须实现,每个item pipeline组件都需要调用该方法, # 这个方法必须返回一个 Item 对象,被丢弃的item将不会被之后的pipeline组件所处理。 return item def open_spider(self, spider): # spider (Spider 对象) – 被开启的spider # 可选实现,当spider被开启时,这个方法被调用。 def close_spider(self, spider): # spider (Spider 对象) – 被关闭的spider # 可选实现,当spider被关闭时,这个方法被调用以下pipeline将所有(从所有’spider’中)爬取到的item,存储到一个独立地items.json 文件,每行包含一个序列化为’JSON’格式的’item’。
打开 pipelines.py 文件,写入下面代码:
import json class MySpiderJsonPipeline(object): def __init__(self): self.file = open('new.json', 'w') def process_item(self, item, spider): content = json.dumps(dict(item), ensure_ascii=False) + "\n" self.file.write(content) return item def close_spider(self, spider): self.file.close()为了启用Item Pipeline组件,必须将它的类添加到 settings.py文件ITEM_PIPELINES 配置,就像下面这个例子:
ITEM_PIPELINES = { # 'mySpider.pipelines.MyspiderPipeline': 300, 'mySpider.pipelines.MySpiderJsonPipeline': 300, }分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,通常将这些数字定义在0-1000范围内(0-1000随意设置,数值越低,组件的优先级越高)
将parse()方法改为4.2中最后思考中的代码,然后执行下面的命令:
scrapy crawl new_chang查看当前目录是否生成new.json
——————————————————————————————————————————————————
Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是您定义爬取的动作及分析某个网页(或者是有些网页)的地方。
class scrapy.Spider是最基本的类,所有编写的爬虫必须继承这个类。
主要用到的函数及调用顺序为:
__init__() : 初始化爬虫名字和start_urls列表
start_requests() 调用make_requests_from url():生成Requests对象交给Scrapy下载并返回response
parse() : 解析response,并返回Item或Requests(需指定回调函数)。Item传给Item pipline持久化 , 而Requests交由Scrapy下载,并由指定的回调函数处理(默认parse()),一直进行循环,直到处理完所有的数据为止。
name
定义spider名字的字符串。
例如,如果spider爬取 mywebsite.com ,该spider通常会被命名为 mywebsite
allowed_domains
包含了spider允许爬取的域名(domain)的列表,可选。
start_urls
初始URL元祖/列表。当没有制定特定的URL时,spider将从该列表中开始进行爬取。
start_requests(self)
该方法必须返回一个可迭代对象(iterable)。该对象包含了spider用于爬取(默认实现是使用 start_urls 的url)的第一个Request。
当spider启动爬取并且未指定start_urls时,该方法被调用。
parse(self, response)
当请求url返回网页没有指定回调函数时,默认的Request对象回调函数。用来处理网页返回的response,以及生成Item或者Request对象。
log(self, message[, level, component])
使用 scrapy.log.msg() 方法记录(log)message。 更多数据请参见 logging
获取片场信息
class TencentItem(scrapy.Item): name = scrapy.Field() detailLink = scrapy.Field() positionInfo = scrapy.Field() peopleNumber = scrapy.Field() workLocation = scrapy.Field() publishTime = scrapy.Field() 编写new_chang.py # -*- coding: utf-8 -*- import scrapy from ..items import MyspiderItem class NewChangSpider(scrapy.Spider): name = 'new_chang' allowed_domains = ['xinpianchang.com'] base_url = "https://www.xinpianchang.com/channel/index/type-/sort-like/duration_type-0/resolution_type-/page-" page = 0 start_urls = [base_url+ str(page)] def parse(self, response): items = [] # for eatch in response.xpath('//li[@class="enter-filmplay"]//div[@class="video-con"]')[:2]: # 将我们得到的数据封装到一个 `MyspiderItem` 对象 item = MyspiderItem() # extract()方法返回的都是unicode字符串 product_little = eatch.xpath('.//a/p/text()').extract() print("*" * 100) # product_type = eatch.xpath('.//div[@class="new-cate"]//span/text()').extract() see_count = eatch.xpath('.//div[@class="video-view fs_12 fw_300 c_b_9"]/span/text()')[0].extract() zan_count = eatch.xpath('.//div[@class="video-view fs_12 fw_300 c_b_9"]/span/text()')[1].extract() print("see_count", zan_count) # xpath返回的是包含元素的列表 item["product_little"] = product_little item["product_type"] = product_type item["see_count"] = see_count item["zan_count"] = zan_count # 返回item给引擎-管道处理 yield item if self.page <= 20: self.page += 1 # 返回request给调度器-下载器-spider.parse(response) # url 表示发送请求的url地址 # callback 表示该请求返回的响应,由指定的函数解析(回调函数) yield scrapy.Request(url=self.base_url + str(self.page), callback=self.parse) # yield scrapy.Request(url=self.start_urls, callback=self.parse) 编写pipeline.py文件 import json class MySpiderJsonPipeline(object): def __init__(self): self.file = open('new.json', 'w') def process_item(self, item, spider): content = json.dumps(dict(item), ensure_ascii=False) + "\n" self.file.write(content) return item def close_spider(self, spider): self.file.close() 在 setting.py 里设置ITEM_PIPELINES ITEM_PIPELINES = { # 'mySpider.pipelines.MyspiderPipeline': 300, 'mySpider.pipelines.MySpiderJsonPipeline': 300, } 执行爬虫:scrapy crawl new_chang