自学Python第二十九天-feapder框架创建爬虫
- 安装
- `feapder` 的设计架构
- `feapder` 框架的简单使用
- 简单创建爬虫
- 简单爬取数据
- 简单的数据保存
- 中间件
- 校验
- 浏览器渲染
- 使用浏览器渲染获取接口数据
feapder
是一款上手简单,功能强大的
Python
爬虫框架,内置
AirSpider
、
Spider
、
TaskSpider
、
BatchSpider
四种爬虫解决不同场景的需求。支持断点续爬、监控报警、浏览器渲染、海量数据去重等功能。更有功能强大的爬虫管理系统
feaplat
为其提供方便的部署及调度。
feapder
设计思路类似于 scrapy
,不过它是国人开发的框架工具,官方文档是中文的,查询比较方便,使用门槛比较低。
官方文档
安装
feapder
需要 python 3.6 以上,且需要依赖特定版的 selenium
等依赖包,所以最好创建单独虚拟环境。另外它有三个版本,区别在于:
- 精简版:不支持浏览器渲染、不支持基于内存去重、不支持入库mongo
pip install feapder
- 浏览器渲染版:不支持基于内存去重、不支持入库mongo
pip install "feapder[render]"
- 完整版:支持所有功能(常用选择)
pip install "feapder[all]"
安装完成后,可以在控制台执行查看是否安装成功
feapder
此时有可能会提示警告:
NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'
这是因为 feapder
建议使用 urllib3 v1
版本。所以最好降级一下
pip install urllib3=1.26.18
feapder
的设计架构
模块名称 | 模块功能 |
---|---|
spider | 框架调度核心 |
parser_control模版控制器 | 负责调度parser |
collector任务收集器 | 负责从任务队里中批量取任务到内存,以减少爬虫对任务队列数据库的访问频率及并发量 |
parser | 数据解析器 |
start_request | 初始任务下发函数 |
item_buffer数据缓冲队列 | 批量将数据存储到数据库中 |
request_buffer请求任务缓冲队列 | 批量将请求任务存储到任务队列中 |
request数据下载器 | 封装了requests ,用于从互联网上下载数据 |
response请求响应 | 封装了response , 支持xpath 、css 、re 等解析方式,自动处理中文乱码 |
feapder
的执行流程为
spider
调度start_request
生产任务start_request
下发任务到request_buffer
中spider
调度request_buffer
批量将任务存储到任务队列数据库中spider
调度collector
从任务队列中批量获取任务到内存队列spider
调度parser_control
从collector
的内存队列中获取任务parser_control
调度request
请求数据request
请求与下载数据request
将下载后的数据给response
,进一步封装- 将封装好的
response
返回给parser_control
(图示为多个parser_control
,表示多线程) parser_control
调度对应的parser
,解析返回的response
(图示多组parser
表示不同的网站解析器)parser_control
将parser
解析到的数据item
及新产生的request
分发到item_buffer
与request_buffer
spider
调度item_buffer
与request_buffer
将数据批量入库
feapder
框架的简单使用
简单创建爬虫
首先创建爬虫,以豆瓣为例:
feapder create -s douban
执行命令后需要手动选择对应的爬虫模板,模板功能如下:
AirSpider
轻量爬虫:学习成本低,可快速上手Spider
分布式爬虫:支持断点续爬、爬虫报警、数据自动入库等功能TaskSpider
分布式爬虫:内部封装了取种子任务的逻辑,内置支持从redis
或者mysql
获取任务,也可通过自定义实现从其他来源获取任务BatchSpider
批次爬虫:可周期性的采集数据,自动将数据按照指定的采集周期划分。(如每7天全量更新一次商品销量的需求)
命令执行成功后选择AirSpider
模板。默认生成的代码继承了feapder.AirSpider
,包含 start_requests
及 parser
两个函数,含义如下:
feapder.AirSpider
:轻量爬虫基类start_requests
:初始任务下发入口feapder.Request
:基于requests
库类似,表示一个请求,支持requests
所有参数,同时也可携带些自定义的参数,详情可参考Requestparse
:数据解析函数response
:请求响应的返回体,支持xpath
、re
、css
等解析方式,详情可参考Response
除了start_requests
、parser
两个函数。系统还内置了下载中间件等函数,具体支持可参考BaseParser
简单爬取数据
feapder
创建了一个默认示例,是可以直接执行的,并且类似于请求头之类的也是默认设置过的(随机)。feapder
的实际使用方法和 scrapy
很类似。不过 feapder
传递数据更加方便。
import feapder
class Douban(feapder.AirSpider):
def start_requests(self):
for page in range(10):
yield feapder.Request(f"https://movie.douban.com/top250?start={page * 25}&filter=")
def parse(self, request, response):
li_list = response.xpath('//ol/li/div[@class="item"]')
for li in li_list:
item = dict()
item['title'] = li.xpath('./div[@class="info"]/div/a/span[1]/text()').extract_first()
item['detail_url'] = li.xpath('./div[@class="info"]/div/a/@href').extract_first()
item['score'] = li.xpath('.//div[@class="star"]/span[2]/text()').extract_first()
# 可以将自定义变量添加到 request 中,例如 item
yield feapder.Request(item['detail_url'], callback=self.parse_detail, item=item)
def parse_detail(self, request, response):
if response.xpath('//div[@class="indent"]/span[@class="all hidden"]//text()'):
request.item['detail_text'] = response.xpath(
'//div[@class="indent"]/span[@class="all hidden"]//text()').extract_first().strip()
else:
request.item['detail_text'] = response.xpath(
'//div[@class="indent"]/span[1]//text()').extract_first().strip()
print(request.item)
if __name__ == "__main__":
# 开启五个线程完成爬虫任务
Douban(thread_count=5).start()
简单的数据保存
feapder
封装好了多种支持的数据库保存方式,只需要设置一下就可以直接保存使用,而不需要手写数据保存逻辑,注意数据库中的表和字段等需要提前创建。
数据库中创建表:
from feapder.db.mysqldb import MysqlDB
db = MysqlDB(ip='localhost', port=3306, user_name='root', user_pass='root', db='py_spider')
sql = """
create table if not exists douban_feapder(
id int primary key auto_increment,
title varchar(255) not null,
score varchar(255) not null,
detail_url varchar(255) not null,
detail_text text
);
"""
db.execute(sql)
# insert ignore: 数据插入,如果数据重复则忽略,例如id重复
insert_sql = """
insert ignore into douban_feapder (id, title, score, detail_url, detail_text) values (
0, '测试数据', 10, 'https://www.baidu.com', '测试数据'
);
"""
db.add(insert_sql)
创建全局设置文件:
feapder create --setting
然后在 settings.py
文件中配置数据库,例如 mysql:
# MYSQL
MYSQL_IP = "localhost"
MYSQL_PORT = 3306
MYSQL_DB = "py_spider"
MYSQL_USER_NAME = "root"
MYSQL_USER_PASS = "root"
或者在爬虫中自定义局部配置
import feapder
class Douban(feapder.AirSpider):
__custom_setting__ = dict(
MYSQL_IP="localhost",
MYSQL_PORT= 3306,
MYSQL_DB="py_spider",
MYSQL_USER_NAME="root",
MYSQL_USER_PASS="root",
)
然后创建 item 文件,item 的字段名是根据数据库的字段名自动生成的
# 在创建items文件之前必须确保文件名与数据库已存在的表名一致
feapder create -i douban_feapder
最后,在爬虫中,将字典类型替换为 item
类型用于数据校验,并直接 yield
此类型的数据,就可以自动入库了。
import feapder
from douban_feapder_item import DoubanFeapderItem
class Douban(feapder.AirSpider):
def start_requests(self):
for page in range(11):
yield feapder.Request(f"https://movie.douban.com/top250?start={page * 25}&filter=")
def parse(self, request, response):
li_list = response.xpath('//ol/li/div[@class="item"]')
for li in li_list:
# 将字典类型替换成DoubanFeapderItem用于数据校验
item = DoubanFeapderItem()
item['title'] = li.xpath('./div[@class="info"]/div/a/span[1]/text()').extract_first()
item['detail_url'] = li.xpath('./div[@class="info"]/div/a/@href').extract_first()
item['score'] = li.xpath('.//div[@class="star"]/span[2]/text()').extract_first()
yield feapder.Request(item['detail_url'], callback=self.parse_detail, item=item)
def parse_detail(self, request, response):
if response.xpath('//div[@class="indent"]/span[@class="all hidden"]//text()'):
request.item['detail_text'] = response.xpath(
'//div[@class="indent"]/span[@class="all hidden"]//text()').extract_first().strip()
else:
request.item['detail_text'] = response.xpath(
'//div[@class="indent"]/span[1]//text()').extract_first().strip()
# 执行yield会将解析好的数据保存到数据库中
yield request.item
if __name__ == "__main__":
Douban().start()
中间件
feapder
中间件只有下载中间件,没有爬虫中间件。并且中间件只是类的一个方法而已,不需要另写一个 py 文件。默认的中间件是 download_midware()
方法,除了可以重写此方法外,还可以自定义下载中间件:使用 Request
对象的 download_midware
参数指定自建创建的中间件方法名
import feapder
class Douban(feapder.AirSpider):
def start_requests(self):
for page in range(11):
yield feapder.Request(f"https://movie.douban.com/top250?start={page * 25}&filter=",
download_midware=self.custom_download_midware)
# 默认的下载中间件
def download_midware(self, request):
request.headers = {
'User-Agent': 'abc'
}
request.proxies = {
"http": "http://127.0.0.1:7890"
}
return request
# 自定义下载中间件
def custom_download_midware(self, request):
request.headers = {
'User-Agent': '123'
}
return request
if __name__ == "__main__":
Douban().start()
需要注意的是,不同于 scrapy
每个请求或响应都会经过所有的中间件处理,feapder
的请求 (feapder
的中间件不处理响应) 只使用一个中间件进行处理。
校验
由于 feapder
的中间件不处理响应对象,所以 feapder
专门有个处理响应的方法,就是 validate()
(校验) 方法。此方法用来检验返回的数据是否正常,可以通过 request.callback_name
来区分不同的回调解析函数,编写不同的校验逻辑。
另外当返回值为 True
或 None
时,进入解析函数;当返回值为 False
时,抛弃响应;当抛出异常时,重试请求(默认最大重试次数100次,也可以引入配置文件或自定义配置进行修改)。另外如果解析函数抛出异常也会进行重试。
class Douban(feapder.AirSpider):
def start_requests(self):
pass
def parse(self, request, response):
pass
def validate(self, request, response):
print('响应状态码:' response.status_code)
if response.status_code != 200:
raise Exception('状态码异常') # 请求重试
if __name__ == "__main__":
Douban().start()
浏览器渲染
feapder
框架包含了浏览器渲染功能,使用 selenium
渲染动态页面,获取渲染后的页面数据。框架内置一个浏览器渲染池,默认的池大小为1,请求时重复利用浏览器实例,只有当代理失效请求异常时,才会销毁、创建一个新的浏览器实例。
使用时,在 start_requests()
方法中,yield
的 feapder.Request
对象的参数中添加 render=True
即可。如需要也可以自定义浏览器渲染配置:
# 在setting.py中有以下代码配置
# 浏览器渲染
WEBDRIVER = dict(
pool_size=1, # 浏览器的数量
load_images=True, # 是否加载图片
user_agent=None, # 字符串 或 无参函数,返回值为user_agent
proxy=None, # xxx.xxx.xxx.xxx:xxxx 或 无参函数,返回值为代理地址
headless=False, # 是否为无头浏览器
driver_type="CHROME", # CHROME、EDGE、PHANTOMJS、FIREFOX
timeout=30, # 请求超时时间
window_size=(1024, 800), # 窗口大小
executable_path=None, # 浏览器路径,默认为默认路径
render_time=0, # 渲染时长,即打开网页等待指定时间后再获取源码
custom_argument=[
"--ignore-certificate-errors",
"--disable-blink-features=AutomationControlled",
], # 自定义浏览器渲染参数
xhr_url_regexes=None, # 拦截xhr接口,支持正则,数组类型
auto_install_driver=True, # 自动下载浏览器驱动 支持chrome 和 firefox
download_path=None, # 下载文件的路径
use_stealth_js=False, # 使用stealth.min.js隐藏浏览器特征
)
可以在解析函数中,使用 selenium
来进行交互和获取数据
import feapder
from selenium.webdriver.common.by import By
from feapder.utils.webdriver import WebDriver
class Baidu(feapder.AirSpider):
def start_requests(self):
yield feapder.Request("https://www.baidu.com", render=True)
def parse(self, request, response):
browser: WebDriver = response.browser
browser.find_element(By.ID, 'kw').send_keys('feapder')
browser.find_element(By.ID, 'su').click()
if __name__ == "__main__":
Baidu().start()
使用浏览器渲染获取接口数据
浏览器打开页面后,可能会通过访问接口获取需要渲染的数据。 feapder
可以直接获取接口数据(因为渲染后可能数据会有变化或编码、加密等)
首先在 setting.py
写入接口的正则,以便拦截动态接口数据
WEBDRIVER = dict(
...
xhr_url_regexes=[
'/ad',
'接口1正则',
'接口2正则',
]
)
这样只要接口符合正则,就会被拦截到。然后可以提取数据
browser: WebDriver = response.browser
# 提取文本
text = browser.xhr_text("接口1正则")
# 提取json
data = browser.xhr_json("接口2正则")
# 获取响应对象
xhr_response = browser.xhr_response('/ad')
print("请求接口", xhr_response.request.url)
print("请求头", xhr_response.request.headers)
print("请求体", xhr_response.request.data)
print("返回头", xhr_response.headers)
print("返回地址", xhr_response.url)
print("返回内容", xhr_response.content