总字符数: 21.88K
代码: 18.29K, 文本: 1.74K
预计阅读时间: 1.45 小时
scrapy的基本使用
- 创建一个工程
scrapy startproject filename
- 必须在
spiders
这个目录下创建一个爬虫文件cd proName
scrapy genspider spiderName www.xxx.com
- 执行工程:
scrapy crawl spiderName
settings.py
- 不遵从
rebots
协议 ROBOTSTXT_OBEY = False
- 进行
UA
伪装 USER_AGENT = 'UA'
- 进行日志等级设定
LOG_LEVEL = 'ERROR'
scrapy解析
1 | def parse(self, response): |
scrapy持久化存储
基于终端指令:
- 要求:只可以将
parse
方法的返回值存储到本地的文本文件中 scrapy crawl qiubai -o ./qiubai.csv
- 注意:持久化存储对应的文本文件类型只可以为:
'json','jsonlines','jl','csv','xml','marshal','pickle'
- 好处:简洁高效便捷
- 缺点:局限性较强(数据值可以存储到指定后缀名文件中)
- 要求:只可以将
基于管道
编码流程:
数据解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#qiubai.py 文件名
import scrapy from qiubaiPro.items import QiubaiproItem#导入item类
class QiubaiSpider(scrapy.Spider):
name = 'qiubai'
# allowed_domains = ['www.xxx.com']
start_urls = ['https://www.qiushibaike.com/text/']
def parse(self, response):
# 解析:作者的名称+段子的内容
div_list = response.xpath('//div[@class="col1 old-style-col1"]/div')
for div in div_list:
# xpath返回的是列表, 但是列表元素一定是Selector类型的对象想要取得内容可以直接.extract()
# .extract可以将Selector对象中的data参数存储的字符串提取出来
# 只要能保证返回的列表里只有一个内容就可以使用.extract_first否则还是使用[下标]取出
# .extract_first将列表中的第0个取出
# 匿名用户和正常用户的用户名不在同一个div中所以使用管道符分割,来写2个xpath表达式
author = div.xpath('./div[1]/a[2]/h2/text() ./div[1]/span[2]/h2/text()').extract_first()
# 列表调用了.extract之后, 则表示将列表中每一个Selector对象中的data对应的字符串提取了出来
content = div.xpath('./a[1]/div/span//text()').extract()
content = ''.join(content) # 将列表转换为字符串在
item
类中定义相关的属性1
2
3
4
5
6
7
8
9
10
11
12
13
14#items.py 文件名
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class QiubaiproItem(scrapy.Item):
# define the fields for your item here like:
author = scrapy.Field()#定义author属性
content = scrapy.Field()#定义content属性将解析的数据封装存储到
item
类型的对象中1
2
3
4#qiubai.py 文件名
item = QiubaiproItem()#实例化对象
item['author'] = author#将author存储到item对象中
item['content'] = content#将content存储到item对象中将
item
类型的对象提交给管道进行持久化存储的操作1
2# 将item提交给了管道
yield item在管道类的
process_item
中要将其接收到的item
对象中存储的数据进行持久化存储操作1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33#pipelines.py 文件名
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class QiubaiproPipeline:
# 专门用来处理item类型对象
# 该方法可以接收爬虫文件提交过来的item对象
# 该方法每接收到一个item就会被调用一次
fp = None
# 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次
def open_spider(self, spider):
print('打开文件')
self.fp = open('./qiubai.txt', 'w', encoding='utf-8')
pass
def process_item(self, item, spider):
author = item['author']
content = item['content']
self.fp.write(author+':'+content+'\n')
return item
def close_spider(self, spider):
print('关闭文件')
self.fp.close()在配置文件中开启管道好处:
1
2
3
4
5#setting.py 文件名
#将以下内容取消注释,300代表优先级,数字越小优先级越高,一个键值对应一个管道类
ITEM_PIPELINES = {
'qiubaiPro.pipelines.QiubaiproPipeline': 300,
}优点:
- 通用性强
缺点:
- 操作繁琐
测试题
- 将爬取到的数据一份存储到本地一份存储到数据库,如何实现?
1
2
3
4
5
6#settings.py 文件名
ITEM_PIPELINES = {
'qiubaiPro.pipelines.QiubaiproPipeline': 300,
'qiubaiPro.pipelines.mysqlPileLine': 301,
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62#pipelines.py 文件名
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
import pymysql
class QiubaiproPipeline:
# 专门用来处理item类型对象
# 该方法可以接收爬虫文件提交过来的item对象
# 该方法每接收到一个item就会被调用一次
fp = None
# 重写父类的一个方法:该方法只在开始爬虫的时候被调用一次
def open_spider(self, spider):
print('打开文件')
self.fp = open('./qiubai.txt', 'w', encoding='utf-8')
pass
def process_item(self, item, spider):
author = item['author']
content = item['content']
self.fp.write(author+':'+content+'\n')
return item # return item就会传递给下一个即将被执行的管道类
def close_spider(self, spider):
print('关闭文件')
self.fp.close()
# 管道文件中一个管道类对应将一组数据存储到一个平台或者载体中
class mysqlPileLine:
conn = None
cursor = None
def open_spider(self, spider):
print('正在连接数据库')
self.conn = pymysql.Connect(
host='127.0.0.1', port=3306, user='root', password='123456789', db='qiubai', charset="utf8")
def process_item(self, item, spider):
print('正在写入数据')
self.cursor = self.conn.cursor()
try:
self.cursor.execute('insert into qiubai values("%s","%s")' %
(item["author"], item["content"]))
self.conn.commit()
except Exception as e:
print(e)
self.conn.rollback
return item
def close_spider(self, spider):
print('正在关闭连接')
self.cursor.close()
self.conn.close()- 爬虫文件提交的
item
类型的对象最终会提交给哪一个管道类?- 爬虫文件提交的
item
只会给管道文件中第一个被执行的管道类接收 - 第一个管道类中的
process_item
中的return item
表示将item
传递给下一个即将被执行的管道类
- 爬虫文件提交的
基于Spider的全站数据爬取
什么是全站数据爬取
就是将网站中某板块下的全部页码对应的页面数据爬取下来
- 需求:爬取校花网中的照片的名称
- 实现方式:
- 自行手动进行请求发送(推荐)
- 将所有页面的
url
添加到start_urls
列表中(不推荐,如果页码有上万个呢?)
1 | #xiaohua.py |
请求传参
- 使用场景:如果爬取的解析数据不在同一张页面中.(深度爬取)
图片数据爬取之ImagesPipeline
基于
scrapy
爬取字符串类型的数据和爬取图片类型的数据区别?- 字符串:只需要
xpath
进行解析且提交管道进行持久化存储 - 图片:
xpath
解析出图片的src
属性值.单独对图片地址发起请求获取图片二进制类型的数据
- 字符串:只需要
ImagesPipeline
:- 只需要将
img
的src
的属性值进行解析,将属性值封装到item
并提交给管道,管道就会对图片的src
进行请求发送获取图片的二进制类型的数据,且还会帮我们进行持久化存储
- 只需要将
需求:爬取站长素材中的高清图片
使用流程:
- 数据解析(地址的地址)
- 将存储文件地址的
item
提交到指定的管道类 - 在管道文件中定制一个基于
ImagesPipeLine
的一个管道类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29#pipelines.py
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
from scrapy.pipelines.images import ImagesPipeline
import scrapy
# class ZhanzhangproPipeline:
# def process_item(self, item, spider):
# return item
class imgsPileLine(ImagesPipeline):
# 就是可以根据图片地址进行图片数据的请求
def get_media_requests(self, item, info):
yield scrapy.Request(item['src'])
# 指定图片存储的路径
def file_path(self, request, response=None, info=None):
imgName = request.url.split('/')[-1]
return imgName
def item_completed(self, results, item, info):
return item # 返回给下一个即将被执行的管道类- 修改
serrings.py
配置文件
1
2
3#serrings.py
#指定图片存储的目录
IMAGES_STORE = './imgs'- 指定开启的管道类:定制的管道类
1
2
3
4
5serrings.py
ITEM_PIPELINES = {
'zhanzhangPro.pipelines.imgsPileLine': 300,
}
中间件
下载中间件
位置:引擎和下载器之间
作用:批量拦截到整个工程中所有的请求和响应
拦截请求
UA
伪装代理
IP
拦截响应:
篡改响应数据,响应对象
设置
UA
伪装以及代理IP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91#middlewares.py
# Define here the models for your spider middleware
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
from scrapy import signals
# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter
import random
class QiubaiproDownloaderMiddleware:
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
# 可被选用的代理IP
PROXY_http = [
'153.180.102.104:80',
'195.208.131.189:56055',
]
PROXY_https = [
'120.83.49.90:9000',
'95.189.112.214:35508',
]
# 拦截请求
def process_request(self, request, spider):
# UA伪装
request.headers['User-Agent'] = random.choice(self.user_agent_list)
return None
# 拦截所有的响应
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
# 拦截发生异常的请求
def process_exception(self, request, exception, spider):
# 发生请求异常后设置代理
# 对拦截到请求的url进行判断(协议头到底是http还是https)
# request.url返回值:http://www.xxx.com
if request.url.split(':')[0] == 'https': # 请求的协议头
ip = random.choice(self.PROXY_https)
request.meta['proxy'] = 'https://'+ip
else:
ip = random.choice(self.PROXY_http)
request.meta['proxy'] = 'http://' + ip
return request # 将修正之后的请求对象进行重新的请求发送在
settings.py
中开启下载中间件
1
2
3DOWNLOADER_MIDDLEWARES = {
'qiubaiPro.middlewares.QiubaiproDownloaderMiddleware': 543,
}需求:爬取网易新闻中的新闻数据(标题和内容)
- 通过网易新闻的首页解析出五大板块对应的详情页
URL
(没有动态加载) - 每一个板块对应的新闻标题都是动态加载出来的(动态加载)
- 通过解析出每一条新闻详情页的
url
获取详情页的页面源码,解析出新闻内容
- 通过网易新闻的首页解析出五大板块对应的详情页
1 | #settings.py |
1 | #wangyi.py |
1 | #middlewares.py |
1 | #items.py |
1 | #pipelines.py |
CrawlSpider类
CrawlSpider类:Spider
的一个子类
- 全站数据爬取的方式
- 基于
Spider
手动请求 - 基于
CrawlSpider
- 基于
CrawlSpider
的使用:- 创建一个工程
cd xxx
- 创建爬虫文件(与之前不一样,基于
CrawlSpider
子类) scrapy genspider -t crawl name www.xxx.com
LinkExtractor
(链接提取器):- 作用:根据指定规则(
allow="正则"
)进行指定链接的提取
- 作用:根据指定规则(
Rule
(规则解析器)- 作用:将链接提取器提取到的链接进行指定规则(
callback
的解析操作)
- 作用:将链接提取器提取到的链接进行指定规则(
分布式爬虫
分布式爬虫的概念
我们需要搭建一个分布式的集群,让其对一组资源进行分布联合爬取
作用
提升爬取数据的效率
如何实现分布式
- 安装
scrapy-redis
的组件 - 原生的
scrapy
是不可以实现分布式爬虫的,必须要让scrapy
结合着scrapy-redis
组件一起实现分布式爬虫 - 为什么原生的
scrapy
不可以实现分布式?- 调度器不可以被分布式集群共享
- 管道不可以被分布式集群共享
scrapy-redis
组件作用- 可以给原生的
scrapy
框架提供可以被共享的管道和调度器
- 可以给原生的
- 实现流程
创建一个工程
创建一个基于
CrawlSpider
的爬虫文件修改当前的爬虫文件
导包
from scrapy_redis.spiders import RedisCrawlSpider
将
start_urls
和allowed_domains
进行注释添加一个新属性:与之代替的是
redis_key='sun'
可以被共享的调度器队列的名称编写数据解析相关的操作
将当前爬虫类的父类修改成
RedisCrawlSpider
修改配置文件
settings.py
- 指定使用可以被共享的管道
1
2
3ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline':400
}- 指定调度器
1
2
3
4
5
6#增加了一个去重容器类的配置,作用使用Redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
#使用scrapy-redis组件自己的调度器
SCHEDULER="scrapy_redis.scheduler.Scheduler"
#配置调度器是否要持久化,也就是当爬虫结束了,要不要清空Redis中请求队列和去重指纹的set.True=保留,False=清空
SCHEDULER_PERSIST=TRUE- 指定
redis
服务器
1
2
3
4#setting.py
REDIS_HOST = 'redis服务器的ip地址'
REDIS_PORT = 6379redis
相关操作配置- 配置`redis`的配置文件
linux/mac:redis.conf
windows:redis.windows.conf
- 打开配置文件修改
- 将
bind 127.0.0.1
进行删除 - 关闭保护模式:
protected_mode yes
改为no
- 将
- 结合着配置文件开启
redis
服务redis-server
配置文件- 启动客户端
redis-cli
- 执行工程
scrapy runspider xxx.py
(爬虫源文件名称)- 向调度器的队列中放入一个起始的
url
- 调度器的队列在redis的客户端中
lpush sun(爬虫文件中的redis_key) www.xxx.com(起始的url)