02_Python|02_Python Scrapy网络爬虫学习
2019.3.30
虽然在寒假时已经自学过了网络爬虫的相关知识,但一是因为当时学习的是使用urllib(在Python3中已经将urliib2包整合到了urllib包中),二是希望通过在跟随老师的系统学习下能够有长足的进步,所以现在打算重头学习网络爬虫。这一篇博客只会讲解scrapy框架的一些知识,不涉及传统爬虫(request、beautiful soup、Xpath等),传统的爬虫之后会在爬虫学习系列详细展开。
文章图片
Scrapy 一.scrapy框架 1. scrapy概述
scrapy官网
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取 (更确切来说,网络抓取)所设计的, 也可以应用在获取API所返回的数据(例如Amazon Associates Web Services) 或者通用的网络爬虫。
Scrapy 使用了 Twisted异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
文章图片
异步与非阻塞的区别.png 2. scrapy 的工作流程
参考:scrapy中文文档
- 图示
文章图片
scarpy工作流程图.png
- 组件的说明(如果不喜欢看文字,下面有图示)
参考:scrapy中文文档
Spider中间件是在引擎及Spider之间的特定钩子(specific hook),处理spider的输入(response)和输出(items及requests)。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。
- Scrapy Engine
引擎负责控制数据流在系统中所有组件中流动,并在相应动作发生时触发事件。- 调度器(Scheduler)
调度器从引擎接受request并将他们入队,以便之后引擎请求他们时提供给引擎。- 下载器(Downloader)
下载器负责获取页面数据并提供给引擎,而后提供给spider。- Spiders
Spider是Scrapy用户编写用于分析response并提取item(即获取到的item)或额外跟进的URL的类。 每个spider负责处理一个特定(或一些)网站。- Item Pipeline
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。- 下载器中间件(Downloader middlewares)
下载器中间件是在引擎及下载器之间的特定钩子(specific hook),处理Downloader传递给引擎的response。 其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy功能。- Spider中间件(Spider middlewares)
- 数据流(Data flow)
- Scrapy中的数据流由执行引擎控制,其过程如下:
- 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
- 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
- 引擎向调度器请求下一个要爬取的URL。
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
- 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
- Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
- 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
- (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
- 事件驱动网络(Event-driven networking)[
Scrapy基于事件驱动网络框架 Twisted编写。因此,Scrapy基于并发性考虑由非阻塞(即异步)的实现。
- 组件的图示
文章图片
scrapy组件说明.png
- scrapy的目录结构
- 创建一个scrapy项目(在命令行内输入)
scrapy startproject
便能创建一个名为priject_name(自己定义)的scrapy项目,其目录有特定的结构。
- 目录结构
以 tutorial 项目为例(源自Python数据分析老师丁烨的pdf文件),scrapy框架会为项目生成以下文件
(ps:由于Python语言并没有类似于Java的打包package语句,所以Python通过一个目录有无init.py来判断一个文件夹是普通文件夹还是一个包,init.py的内容可以为空,存在即可)
文章图片
scrapy项目的目录结构.png
- 创建一个继承于scrapy.Spider的类来实现爬虫
其中,start_request()和parse()都是需要通过重写来实现的函数
import scrapy# 这个类可以获取html资源源码
class QuotesSpider(scrapy.Spider):
# 这个类是继承于scrapy.Spider类的# 爬虫的名字,是服务器端识别爬虫的标识
name = "quotes"# Overwrite the start_requests()
def start_requests(self):
# start_requests方法必须返回可迭代的Request对象,这个spider将这些对象作为起始初始化请求对象# 原始版本
"""
urls = ['http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/']
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
"""# 改进版本(添加分页识别机制)
url = 'http://quotes.toscrape.com/'
tag = getattr(self, 'tag', None)
if tag is not None:
url = url + 'tag/' + tag
yield scrapy.Request(url, self.parse)
- parse()函数,重写模板函数,用于解析html网页
def parse(self, response):
# 在parse方法中每一个request请求发出之后会在这个方法中返回对应的response对象,
# 这个response对象为textResponse对象,包含了界面的内容和其他一些处理的方法。
# 在这个方法内部通常将数据分解为字典,还可以查找到新的url# 原始版本
# 获取某些标签的内容
for quote in response.css('div.quote'):
yield {
'text': quote.css("span.text::text").get(),
'author': quote.css("small.author::text").get(),
'tags': quote.css("div.tags a.tag::text").getall(),
# 这个逗号也是必要的
}
- 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型。
- 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息。
- scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取。
- 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
- parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse)
- Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路)
- 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
- 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items。
- 这一切的一切,Scrapy引擎和调度器将负责到底。
- 爬虫的运行
- 运行命令(命令行输入,quotes可变,指的是爬虫的名字,自己定义)
scrapy crawl quotes
【02_Python|02_Python Scrapy网络爬虫学习】(ps:成功爬取之后,会生成以上的文件(json文件除外))
- 爬行结果
文章图片
爬虫运行结果01.png
文章图片
爬虫运行结果02.png
文章图片
爬虫运行结果03.png
- 将爬取网页中的内容保存到一个json文件中(不一定要这么做)
- 运行命令(命令行输入)
scrapy crawl quotes -o quotes.json
- json文件内容
文章图片
json文件内容.png
- 完整源代码
import scrapy# 这个类可以获取html资源源码
class QuotesSpider(scrapy.Spider):
# 这个类是继承于scrapy.Spider类的# 爬虫的名字,是服务器端识别爬虫的标识
name = "quotes"# Overwrite the start_requests()
def start_requests(self):
# start_requests方法必须返回可迭代的Request对象,这个spider将这些对象作为起始初始化请求对象# 原始版本
"""
urls = ['http://quotes.toscrape.com/page/1/', 'http://quotes.toscrape.com/page/2/']
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
"""# 改进版本(添加分页识别机制)
url = 'http://quotes.toscrape.com/'
tag = getattr(self, 'tag', None)
if tag is not None:
url = url + 'tag/' + tag
yield scrapy.Request(url, self.parse)def parse(self, response):
# 在parse方法中每一个request请求发出之后会在这个方法中返回对应的response对象,
# 这个response对象为textResponse对象,包含了界面的内容和其他一些处理的方法。
# 在这个方法内部通常将数据分解为字典,还可以查找到新的url# 原始版本
"""
# 获取某些标签的内容
for quote in response.css('div.quote'):
yield {
'text': quote.css("span.text::text").get(),
'author': quote.css("small.author::text").get(),
'tags': quote.css("div.tags a.tag::text").getall(),
# 这个逗号也是必要的
}# str.split(str="", num=string.count(str)), num默认-1, 即分割全部
# 获得倒数第2个分割子串,也就是页数(1 或 2)
page = response.url.split("/")[-2]
filename = 'quotes-{}.html'.format(page)
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file {}'.format(filename))
"""# 改进版本(添加分页识别机制,可爬多个页面)
"""
for quote in response.css('div.quote'):
yield {
'text': quote.css("span.text::text").get(),
'author': quote.css("small.author::text").get(),
}
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
# 继续爬下一个页面
yield response.follow(next_page, self.parse)
"""# 作业的代码(随着作者的信息返回页面的内容)
for href in response.css('.author + a::attr(href)'):
yield response.follow(href, self.parse_author)next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
# 继续爬下一个页面
yield response.follow(next_page, self.parse)def parse_author(self, response):
def extract_with_css(query):
return response.css(query).get(default='').strip()
yield {
'name': extract_with_css('h3.author-title::text'),
'birthdate': extract_with_css('.author-born-date::text'),
'bio': extract_with_css('.author-description::text'),
}
推荐阅读
- parallels|parallels desktop 解决网络初始化失败问题
- 猎杀IP
- python学习之|python学习之 实现QQ自动发送消息
- 逻辑回归的理解与python示例
- python自定义封装带颜色的logging模块
- 【Leetcode/Python】001-Two|【Leetcode/Python】001-Two Sum
- Python基础|Python基础 - 练习1
- 自媒体形势分析
- 数学大作战
- Python爬虫|Python爬虫 --- 1.4 正则表达式(re库)