-
- 1 安装
- 2 框架组成
- 3 工作原理
- 4 如何使用
- 5 保存数据的流程
-
- 如果要把数据通过`json`形式 保存在文件的话,那么 pipeline 文件中,应该使用 JsonLinesItemExporter 方法
- 在爬虫文件中,请求其他的链接地址
- 传递请求的参数
- 获取传递的参数
- 如果项目初始的请求方式就是 post 的话, 那么需要 在 爬虫文件中 重写 start_request 方法
- scrapy中 中间件的使用,scrap有两种中间件,一个是爬虫中间件(这个一般用不到), 一个是 下载器 中间件。中间件,用的比较多的方法是 process_request, 这个方法是设置 请求头中的一些参数,和返回响应的 自定义的 源代码, 一般最常用的就是 设置 ip 代理 和 随机 请求头
- 注意:一定要在 配置文件中 打开 中间件的配置
- 了解: 如果有多个中间件,那么中间件中 方法的执行顺序
- 在scrapy中使用selenium
- 6 规则爬虫的流程
1 安装
pip install scrapy
2 框架组成
引擎(engine)
自动运行,无需关注,会自动组织所有的请求对象,分发给下载器
下载器(downloader)
从引擎处获取到请求对象后,请求数据
爬虫spiders
scrapy.Spider 普通的爬虫
scrapy.CrawlSpider
可设置规则的爬虫类
Rule 规则类
开始的函数
start_requests()
调度器(scheduler)
管道(Item pipeline)
1. 清理HTML数据
2. 验证爬取的数据(检查item包含某些字段)
3. 查重(并丢弃)
4. 将爬取结果保存到数据库中
5. 对图片数据进行下载
3 工作原理
流程
1、爬虫引擎获得初始请求开始抓取。
2、爬虫引擎开始请求调度程序,并准备对下一次的请求进行抓取。
3、爬虫调度器返回下一个请求给爬虫引擎。
4、引擎请求发送到下载器,通过下载中间件下载网络数据。
5、一旦下载器完成页面下载,将下载结果返回给爬虫引擎。
6、引擎将下载器的响应通过中间件返回给爬虫进行处理。
7、爬虫处理响应,并通过中间件返回处理后的items,以及新的请求给引擎。
8、引擎发送处理后的items到项目管道,然后把处理结果返回给调度器,调度器计划处理下一个请求抓取。
9、重复该过程(继续步骤1),直到爬取完所有的url请求
4 如何使用
创建项目
终端输入scrapy startproject 项目名称
目录结构
spiders
__init__.py
自定义的爬虫文件.py
__init__.py
items.py
middlewares.py
pipelines.py
settings.py
创建自定义爬虫文件
scrapy genspider 爬虫名字 网页的域名
继承scrapy.Spider类
name = 'qiubai'
allowed_domains
start_urls
parse(self, response)
response 是 scrapy.http.HtmlResponse类对象
response的属性
response.encoding
字符集,不能直接修改
response.text
文本信息
response.body
字节数据
response.headers
头
response.meta
元信息
用于从请求向响应的解析函数中传递参数
response.request
响应的哪一个请求对象
response.url
response.xpath()/css()
scrapy.selector.Selector
返回Selector对象
内部写法:self.selector.xpath()
css()用法
同css的样式选择器,如id,class或标签等
访问标签内的属性
'#page a::attr("href")'
获取id为page的a标签的href属性
'.list-img a::text'
获取class为list-img下的a标签的text文本内容
xpath()用法
同lxml的xpath用法,主要是针对的路径
提取数据
extract()
Selector对象的方法,用于获取Selector对象的内容
response.xpath('//title/text()').extract()
返回list
response.css('').xpath()
先使用css选择标签元素,再通过xpath提取内容
extract_first()
提取第一条内容
get()
同 extract_first()方法
修改settings.py代码
ROBOTSTXT_OBEY设置为False。默认是True。即遵守机器协议,那么在爬虫的时候,scrapy首先去找robots.txt文件,如果没有找到。则直接停止爬取。
DEFAULT_REQUEST_HEADERS添加User-Agent。这个也是告诉服务器,我这个请求是一个正常的请求,不是一个爬虫。
解析数据回传给engine
yield item
向engine发起新的请求
yield scrapy.Request(url, callback=,meta=, dont_filter=True)
运行程序
scrapy crawl 爬虫名称
导出文件
-o name.json
-o name.xml
-o name.csv
eg
scrapy crawl dy -o vides.json
items.py
import scrapy
class QsbkItem(scrapy.Item):
author = scrapy.Field()
content = scrapy.Field()
pipeline.py
open_spider
process_item
close_spider
JsonItemExporter
JsonLinesItemExporter
运行scrapy项目
from scrapy import cmdline
cmdline.execute(“scrapy crawl qsbk”.split())
5 保存数据的流程
1、在爬虫文件的 parse 方法中,只用来获取数据,不要做保存数据的操作,因为这样用不到多线程
所以要通过 yield 把封装的数据 交给 pipeline
2、在 配置文件中, 把 pipeline 的配置打开
ITEM_PIPELINES = {
'douban.pipelines.DoubanPipeline': 300,
# 如果有多个 pipeline路径,那么这些pippeline都会去 处理 yield 返回的数据
# 值代表的是 权重, 值越小 越先执行
'douban.pipelines.DoubanPipeline1': 310,
}
3、在 pipeline 文件中, 定义如下方法
class DoubanPipeline:
# open_spider 在爬虫运行 开始的时候 先执行,相当于 __init__ 方法
# 参数spider 的作用是,区分是哪个爬虫
# 比如现在有两个爬虫
'''
spider
__init__.py
movie.py # 爬虫
rank.py # 爬虫
'''
def open_spider(self, spider):
if spider.name == 'movie':
self.fp = open('movie.json', 'a', encoding='utf-8')
elif spider.name == 'rank':
self.fp1 = open('rank.json', 'a', encoding='utf-8')
def process_item(self, item, spider):
if spider.name == 'movie':
self.fp.write(json.dumps(item, ensure_ascii=False))
elif spider.name == 'rank':
self.fp1.write(json.dumps(item, ensure_ascii=False))
return item
# 爬虫结束后执行,相当于 __del__方法
def close_spider(self, spider):
if spider.name == 'movie':
self.fp.close()
elif spider.name == 'rank':
self.fp1.close()
# 一般做法是 在一个项目中 创建一个 爬虫, 那就没有必要 去判断 爬虫
如果要把数据通过json
形式 保存在文件的话,那么 pipeline 文件中,应该使用 JsonLinesItemExporter 方法
from scrapy.exporters import JsonLinesItemExporter
class DoubanPipeline:
def open_spider(self, spider):
self.fp = open('douban.json', 'ab')
self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')
def process_item(self, item, spider):
self.exporter.export_item(item)
return item
def close_spider(self, spider):
self.fp.close()
在爬虫文件中,请求其他的链接地址
-
在 parse 方法中,通过 yield scrapy.Request 方法请求地址, Request 方法中有两个必填的参数
扫描二维码关注公众号,回复: 12905622 查看本文章参数:
- url:请求的网址
- callback:回调函数,当爬虫获取到请求的源代码之后,会交给 callback 定义的方法来处理数据
def parse(self, response): # 爬取 列表页面中,每个新闻 的 内容页的链接 # 访问 每个内容页的 链接,获取数据 # response.urljoin 在不完整的地址前面补充 域名 hrefs = [response.urljoin(href) for href in response.xpath('//*[@id="itemContainer"]/div/div/h3/a/@href').getall()] for href in hrefs: # 怎么用 scrapy 去访问每一个连接地址 yield scrapy.Request(url=href, callback=self.parse_detail) def parse_detail(self, response): title = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()').get() desc = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[3]/p/text()').get() item = WxappItem(title=title, desc=desc) yield item
传递请求的参数
方法: scrapy.Request( url, callback, meta={‘k’:‘v’, ‘k’: ‘v’}) meta就是传递的参数,字典形式
获取传递的参数
方法:response.meta.get(‘k’) 通过响应对象的 response 的 meta 属性,格式是字典
如果项目初始的请求方式就是 post 的话, 那么需要 在 爬虫文件中 重写 start_request 方法
def start_requests(self):
data = {
'kw': 'world'
}
yield scrapy.FormRequest(url=self.start_urls[0], formdata=data, callback=self.post_data)
scrapy中 中间件的使用,scrap有两种中间件,一个是爬虫中间件(这个一般用不到), 一个是 下载器 中间件。中间件,用的比较多的方法是 process_request, 这个方法是设置 请求头中的一些参数,和返回响应的 自定义的 源代码, 一般最常用的就是 设置 ip 代理 和 随机 请求头
-
ip代理的设置方式
class downloader1: proxy_list = [ '119.132.76.144:4256', '182.135.158.137:4226', '42.242.122.222:4243' ] def process_request(self, request, spider): data = random.choice(self.proxy_list) request.meta['proxy'] = f'http://{data}' def process_response(self, request, response, spider): return response
-
随机请求头的方式
class downloader2: USER_AGENTS = [ "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36", ] def process_request(self, request, spider): data = random.choice(self.USER_AGENTS) request.headers['user-agent'] = data def process_response(self, request, response, spider): return response
注意:一定要在 配置文件中 打开 中间件的配置
DOWNLOADER_MIDDLEWARES = {
# 'httpbin.middlewares.HttpbinDownloaderMiddleware': 543,
# 'httpbin.middlewares.downloader1': 543,
'httpbin.middlewares.downloader2': 544,
}
了解: 如果有多个中间件,那么中间件中 方法的执行顺序
-
process_request 返回 None
process_request 的方法是 配置文件中 中间件定义 顺序的 从上往下 执行
process_resposne 的方法是 从下往上 执行
-
process_request 返回响应对象
不会再执行 下一个 中间件的 process_request 方法,
process_response 方法 还是 从下往上执行
爬虫中 获取的 源代码是 响应对象中设置的 代码,比如
def process_request(self, request, spider) return HtmlResponse(url=request.url, body=b'返回的内容', request=request)
在scrapy中使用selenium
在中间件中设置
# 通过seelnium拿到网站的源码
# 再通过 process_request 返回响应对象的同时,把源码加入到 响应的body
class SeleniumMiddware:
def __init__(self):
chrome_path = chrome_path = r'C:\Users\apple\Desktop\soft\chromedriver.exe'
self.driver = webdriver.Chrome(executable_path=chrome_path)
def process_request(self, request, spider):
self.driver.get(request.url)
data = self.driver.page_source
return HtmlResponse(url=request.url, body=data, request=request, encoding='utf-8')
6 规则爬虫的流程
-
创建规则爬虫
- 创建项目
- 创建规则爬虫:
scrapy genspider -t crawl 爬虫名称 域名
-
最重要的就是创建 正则去 匹配 要请求的网址
class AppSpider(CrawlSpider): name = 'app' allowed_domains = ['dreawer.com'] start_urls = ['http://wxapp.dreawer.com/portal.php?mod=list&catid=2&page=1'] # 重要 rules = ( # LinkExtractor(allow=r'') 在获取的初始页面的源代码中 去 匹配 allow参数中 链接地址的正则,如果能匹配到,就去请求这个页面 # callback 把请求到的页面源码交给 callback 方法去 获取特定数据 # follow 表示跟随,如果为 True,表示请求其他页面地址的时候,继续去匹配路由正则 # 如果为 Fasle,表示当请求其它页面,不再去 匹配路由正则 Rule(LinkExtractor(allow=r'.+mod=list&catid=2&page=\d+'), follow=True), Rule(LinkExtractor(allow=r'.+article-\d+-\d+\.html'), callback='parse_item', follow=False), )
scrapy-redis 项目的搭建,比如以 爬取 小程序社区
为例
-
创建一个普通的规则爬虫
- 创建项目
- 创建爬虫 scrapy genspider -t crawl 爬虫名称 域名
-
在 爬虫程序里面去写 爬虫规则
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class AppSpider(CrawlSpider): name = 'app' allowed_domains = ['dreawer.com'] start_urls = ['http://wxapp.dreawer.com/portal.php?mod=list&catid=2&page=1'] rules = ( # LinkExtractor(allow=r'') 在获取的初始页面的源代码中 去 匹配 allow参数中 链接地址的正则,如果能匹配到,就去请求这个页面 # callback 把请求到的页面源码交给 callback 方法去 获取特定数据 # follow 表示跟随,如果为 True,表示请求其他页面地址的时候,继续去匹配路由正则 # 如果为 Fasle,表示当请求其它页面,不再去 匹配路由正则 Rule(LinkExtractor(allow=r'.+mod=list&catid=2&page=\d+'), follow=True), Rule(LinkExtractor(allow=r'.+article-\d+-\d+\.html'), callback='parse_item', follow=False), ) def parse_item(self, response): con = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()').get() print(con)
-
能够 让这个 规则爬虫 先 运行起来
-
修改 爬虫中 的一些设置,让爬虫能够使用 scrapy_redis 的 逻辑
-
把 爬虫继承的 CrawlSpider父类 改成 RedisCrawlSpider
-
去掉 start_urls
-
在 爬虫类 添加 redis_key 属性,可以把这个属性 看成 redis 队列的 名称,它的值一般叫做 redis_key = ‘start:url’
整个爬虫类 应该是一下写法
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from scrapy_redis.spiders import RedisCrawlSpider from ..items import AppredisItem class AppSpider(RedisCrawlSpider): name = 'app' allowed_domains = ['dreawer.com'] # start_urls = ['http://dreawer.com/'] redis_key = 'start:url' rules = ( # LinkExtractor(allow=r'') 在获取的初始页面的源代码中 去 匹配 allow参数中 链接地址的正则,如果能匹配到,就去请求这个页面 # callback 把请求到的页面源码交给 callback 方法去 获取特定数据 # follow 表示跟随,如果为 True,表示请求其他页面地址的时候,继续去匹配路由正则 # 如果为 Fasle,表示当请求其它页面,不再去 匹配路由正则 Rule(LinkExtractor(allow=r'.+mod=list&catid=2&page=\d+'), follow=True), Rule(LinkExtractor(allow=r'.+article-\d+-\d+\.html'), callback='parse_item', follow=False), ) def parse_item(self, response): title = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()').get() desc = response.xpath('//*[@id="ct"]/div[1]/div/div[1]/div/div[3]/p/text()').get() item = AppredisItem( title=title, desc=desc ) yield item
-
-
修改 配置文件
-
启用Redis调度存储请求队列: 添加配置是
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
-
确保所有的爬虫通过Redis去重: 添加配置是
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
-
处理yield 返回的数据,是通过 scrapy_redis默认的pipeline进行,这个pipeline默认是把数据保存在 redis的队列中
添加配置:
ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 300 }
-
不清除Redis队列、这样可以暂停/恢复 爬取: 添加配置
SCHEDULER_PERSIST = True
-
指定连接到redis时使用的端口和地址:
添加配置
REDIS_HOST = '127.0.0.1' REDIS_PORT = 6379
-
-
开启 redis 服务器, 找到 redis 的安装目录, 双击 下面的 redis-server.exe 程序
-
如果有多个 服务器,那么就需要把 项目代码 放入到 多个服务器中,进入项目,执行爬虫. 注意 redis的ip应该是 存放 redis服务的 外网 ip, 状态是阻塞的,因为没有获取到第一个 url
-
打开 redis 的客户端, 找到 redis 的安装目录,点击下面的 redis-cli.exe 的程序
-
添加一个 redis的 队列,名字叫做 start:url , 值就是 第一个要 爬起的 页面, 命令就是
lpush start:url http://wxapp.dreawer.com/portal.php?mod=list&catid=2
-
添加完成之后, 所有的爬虫 由 阻塞状态 就会变成 爬取数据的 状态
-
爬取数据后,在 redis 里面会创建 3个 变量
- dupefilter: 保存去重的网址
- items: 保存的是爬取的数据
- requests: 即将要 爬取的网址