为什么要学习Scrapy:可以大幅提升爬虫的效率。
什么是Scrapy:⼀个为了爬取网站数据,提取结构性数据而编写的应用框架。
Scrapy的优点:
同步与异步
异步:调用在发出之后,这个调用就直接返回,不管有无结果;
非阻塞:关注的是程序在等待调用结果时的状态,指在不能立刻得到结果之前,该调用不会阻塞当前线程。
Scrapy参考链接:
https://scrapy-chs.readthedocs.io/zh_CN/1.0/intro/overview.html
https://scrapy.org/
> pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ scrapy
一种常见的多线程爬虫的流程图如下:
笔者之前写的多线程爬取王者荣耀高清壁纸采用的就是上述思路。
Scrapy框架中各组成部分如下:
下载中间件和爬虫中间件是负责引擎和下载器还有引擎和爬虫程序之间的数据传输。
给出百度上用到的流程图:
笔者课程所用图:
Scrapy模块的功能表格:
部件名称 | 功能作用 | 是否需手写 |
---|---|---|
Engine(引擎) | 总指挥,负责数据和信号的在不同模块间的传递 | Scrapy已经实现 |
Scheduler(调度器) | ⼀个队列,存放引擎发过来的request请求 | Scrapy已经实现 |
Downloader(下载器) | 下载把引擎发过来的requests请求,并返回给引擎 | Scrapy已经实现 |
Spider(爬虫程序) | 处理引擎发来的response,提取数据和url,并交给引擎 | 需要手写 |
Item Pipline(管道) | 处理引擎传过来的数据,比如存储 | 需要手写 |
Downloader Middlewares(下载中间件) | 可以⾃定义的下载扩展,比如设置代理 | ⼀般不用手写 |
Spider Middlewares(爬虫中间件) | 可以⾃定义requests请求和进行response过滤 | ⼀般不用手写 |
第一步,创建Scrapy项目。
> scrapy startproject project_name
第二步,创建爬虫的程序。
> cd project_name
> scrapy genspider example example.com
example指爬虫程序的名字,example.com指想爬取网站的域名(范围)。
创建爬虫程序后的文件目录结构如下:
第三步,获取到response后,使用xpath方法提取所需的数据。
第四步,编写piplines管道代码,用于保存数据。
items.py:可用于封装数据,以字典的形式。
import scrapy
class FirstscrapyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
middlewares.py:下载中间件和爬虫中间件的代码。
from scrapy import signals
# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter
# 这里为方便显示,先将内部代码设为pass
class FirstscrapySpiderMiddleware:
pass
class FirstscrapyDownloaderMiddleware:
pass
piplines.py:管道,使用时不要忘记打开管道设置。
from itemadapter import ItemAdapter
class FirstscrapyPipeline:
def process_item(self, item, spider):
return item
settings.py:存储全局配置,这里给出常用的一些配置:
# 是否遵守robots协议,一般为False
ROBOTSTXT_OBEY = False
# 设置日志等级
LOG_LEVEL = 'WARNING'
# 最大并发量,默认为16
# Configure maximum concurrent requests performed by Scrapy (default: 16)
CONCURRENT_REQUESTS = 16
# 下载延迟为3s,默认注释,即没有下载延迟
DOWNLOAD_DELAY = 3
# 请求报头,可以添加user_agent等
DEFAULT_REQUEST_HEADERS = {
'User_Agent': 'Mozilla/5.0 ****',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
# 爬虫中间件
SPIDER_MIDDLEWARES = {
'firstscrapy.middlewares.FirstscrapySpiderMiddleware': 543,
}
# 下载中间件
DOWNLOADER_MIDDLEWARES = {
'firstscrapy.middlewares.FirstscrapyDownloaderMiddleware': 543,
}
# 设置管道
ITEM_PIPELINES = {
'firstscrapy.pipelines.FirstscrapyPipeline': 300,
}
代码需求:爬取豆瓣网页的一些文字标签。
数据标签分布图:
本案例中,我们所需要爬取的数据在class
属性为"side-links nav-anon"
的div/li
标签下。
在正式编写代码前,笔者先梳理一下必要的准备工作。
首先,配置settings.py的文件内容如下:
BOT_NAME = 'firstscrapy'
SPIDER_MODULES = ['firstscrapy.spiders']
NEWSPIDER_MODULE = 'firstscrapy.spiders'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
# 下载延迟为1秒
DOWNLOAD_DELAY = 1
# 请求报头# 请求报头
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
# 管道
ITEM_PIPELINES = {
'firstscrapy.pipelines.FirstscrapyPipeline': 300,
}
其次,Scrapy框架为用户自动生成的基础爬虫爬虫程序如下:
import scrapy
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['douban.com']
start_urls = ['http://douban.com/']
def parse(self, response):
pass
按照之前提及的Scrapy工作流程,爬虫程序向引擎提交url后,引擎发送给调度器进行入列,然后会给下载器,对目标url访问获得响应(网页源码),其返回的结果就是上述代码中的response。在拿到response后,我们尝试解析这个数据,笔者在程序开头导入如下变量(导入该变量不会影响程序,实际使用也不需要该语句,这里只是为了方便演示):
from scrapy.http.response.html import HtmlResponse
点击HtmlResponse
,查看其源码,可以看到它里面有xpath、css等方法,如下图(如果不知道具体模块,也可以直接打印其数据类型进行查找):
有了xpath方法后,在程序中就可以向之前一样使用xpath获取想要的标签内容或属性。
先写一个简单的代码,获取到response后用xpath语句,提取上图想要标签的内容:
import scrapy
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['douban.com']
start_urls = ['http://douban.com/']
def parse(self, response):
item={
}
text_list = response.xpath('//div[@class="side-links nav-anon"]/ul/li')
print(type(response)) # 打印response的数据类型
print(type(text_list[0])) # 打印列表中元素的数据类型
for text in text_list:
item['name'] = text.xpath('a/em/text()').extract_first()
if item['name'] == None:
item['name'] = text.xpath('a/text()').extract_first()
print(item['name'])
如何运行Scrapy爬虫程序
scrapy crawl spider_name
from scrapy import cmdline
cmdline.execute("scrapy crawl spider_name".split()) # 第一种写法
cmdline.execute(['scrapy','crawl','spider_name']) # 第二种写法
运行结果:
上面的截图中最上方显示Spider opened
,说明开始执行自定义的爬虫程序。上文已分析过,下载器返回的response对象可以使用xpath方法,其返回的是一个Selector对象列表。我们可以继续对Selector对象使用xpath方法,如果想进一步提取Selector对象中的数据,有如下方法:
上述代码中笔者使用的是extract_first()方法,也可以使用get()方法。
代码中还有一个小细节,在上述代码中先找'a/em/text()'
, 若判断其为空才找'a/text()'
,是为了跳过含有em标签的a标签,确保所要寻找数据的一致性,如下图:
我们在爬取到数据后,需要处理或者保存数据,可以交给piplines管道执行,这也是scrapy框架设定的管道职责。
如何将爬取到的数据交给piplines:
# 设置管道
ITEM_PIPELINES = {
'firstscrapy.pipelines.FirstscrapyPipeline': 300,
}
def parse(self, response):
xxxxxx
yield item
爬虫程序代码:
import scrapy
class DbSpider(scrapy.Spider):
name = 'db'
allowed_domains = ['douban.com']
start_urls = ['http://douban.com/']
def parse(self, response):
item={
}
text_list = response.xpath('//div[@class="side-links nav-anon"]/ul/li')
for text in text_list:
item['name'] = text.xpath('a/em/text()').extract_first()
if item['name'] == None:
item['name'] = text.xpath('a/text()').extract_first()
yield item
piplines管道代码:
class FirstscrapyPipeline:
def process_item(self, item, spider):
print(item)
return item
运行结果:
小拓展——open_spider()/close_spider()
修改后的piplines代码:
import json
class FirstscrapyPipeline:
# 注意方法名不能写错,要传递一个形参
def open_spider(self, item):
print('This is open_spider method.')
def process_item(self, item, spider):
with open('result.json', 'a', encoding='utf-8') as fobj:
# 传递关键字参数ensure_ascii=False,确保数据正确性
item_json = json.dumps(item, ensure_ascii=False)
# 每行写一个元素
fobj.write(item_json + '\n')
return item
def close_spider(self, item):
print('This is close_spider method.')