Spider
目前提供以下 spider
- LocalSpider:本地爬虫
- DistributiveSpider:分布式爬虫
- CycleSpider:周期爬虫(根据需求可继承本地、分布式)
- JumpSpider:跳转爬虫(基于内存队列,用于频繁的验证码、登录的确认更新 cookie)
LocalSpider
本地爬虫,不支持分布式
创建
注意:在项目目录下执行
palp create -s baidu 1
结构
"""
Create on 2022-12-12 16:21:10.159221
----------
@summary:
----------
@author:
"""
import palp
class BaiduSpider(palp.LocalSpider):
spider_name = "baidu" # 自定义的名字
spider_domains = [] # 允许通过的域名,默认不限制
spider_settings = None # 字典形式或导入形式的设置
def start_requests(self) -> None:
"""
起始函数
:return:
"""
yield palp.RequestGet("https://www.baidu.com")
def parse(self, request, response) -> None:
"""
解析函数
:param request:
:param response:
:return:
"""
print(response)
if __name__ == '__main__':
BaiduSpider().start()
DistributiveSpider
注意:
- 分布式爬虫,需要在设置中增加 redis 连接
- 分布式爬虫默认为优先级队列,本身具有去重的功能,如果有大量必要的重复请求,修改 settings.REQUEST_QUEUE_MODE = 1
创建
palp create -s baidu 2
结构
和普通的爬虫,只是继承类不同,无成本切换
"""
Create on 2022-12-12 16:21:10.159221
----------
@summary:
----------
@author:
"""
import palp
class BaiduSpider(palp.DistributiveSpider):
spider_name = "baidu" # 自定义的名字
spider_domains = [] # 允许通过的域名,默认不限制
spider_settings = None # 字典形式或导入形式的设置
def start_requests(self) -> None:
"""
起始函数
:return:
"""
yield palp.RequestGet("https://www.baidu.com")
def parse(self, request, response) -> None:
"""
解析函数
:param request:
:param response:
:return:
"""
print(response)
if __name__ == '__main__':
BaiduSpider().start()
borrow_request、recycle_request
目前框架是根据以上两个函数实现 cookie_jar 共享功能(可自定义)
- borrow_request:决定在什么时候借用共享的参数
- recycle_request:决定在执行完毕时,共享什么参数
在处理批次任务时,可能一次会有很多任务,对每个任务都去重新请求,获取 cookie 的话(指的是登录、验证码等获取必要 cookie)将会是灾难
所以这里解决 cookie 复用,当然要做好 cookie 失效的判断,并重新登录(可在 middleware 判断,并直接返回含有 jumpspider 的请求)
注意:该机制在任何时候都会生效,即你发出去的请求,只要没有 cookie 就会被设置
这里说另外一个逻辑
假设需求是开始需要获取 cookie,但是我们不能每次都通过 JumpSpider 获取
上面的逻辑不适用,因为这里开始时是直接返回的的 JumpSpider
所以这里可以判断一下,能借到 cookie 就借,借不到就通过 JumpSpider 获取
recycle_data = self.queue_borrow.get(block=False)
if recycle_data:
if 'cookie_jar' in recycle_data:
yield palp.RequestGet(
url=url,
params=params,
headers=headers,
meta={'name': name, 'task': task},
keep_cookie=True,
cookie_jar=recycle_data['cookie_jar']
)
else:
yield palp.RequestGet(
url=url,
params=params,
headers=headers,
meta={'name': name, 'task': task},
jump_spider=TycJumpSpider, # 导入 JumpSpider(不需要实例化)
jump_request_middleware=RequestMiddleware, # 导入请求中间件(不需要实例化)
keep_cookie=True
)
settings 开启
REQUEST_BORROW = False # 分发大量任务时,需要在请求中传递的参数(默认回收 cookie 复用)
REQUEST_BORROW_DELETE_WHEN_START = True # 启动时删除所有 BORROW(非分布式是在内存里,该选项忽略)
spider 回收
因为 spider 内无法判断是否是最后一个请求,所以需要主动回收
self.recycle_request(request=request)
middleware 判断
注意:这只是个案例
该案例是在请求结束时,判断到当前状态不对,随后修改请求,跳转登录,放弃原请求的同时,发出新的请求
def request_close(self, spider, request, response):
if response.status_code != 200:
request.jump_spider = LoginJumpSpider
request.jump_request_middleware = ProxyRequestMiddleware
return request
案例
"""
Create on 2023-01-03 10:45:25.681820
----------
@summary:
----------
@author:
"""
import palp
class BaiduLocalSpider(palp.DistributiveSpider):
spider_name = "baidu" # 自定义的名字
spider_domains = [] # 允许通过的域名,默认不限制
spider_settings = None # 字典形式或导入形式的设置
def start_requests(self) -> None:
"""
起始函数
:return:
"""
yield palp.RequestGet("https://www.baidu.com", keep_cookie=True)
yield palp.RequestGet("https://www.baidu.com", keep_cookie=True)
def parse(self, request, response) -> None:
"""
解析函数
:param request:
:param response:
:return:
"""
print(request.cookie_jar.get_cookie()) # 可以看到两次是一样的 cookie
self.recycle_request(request=request)
if __name__ == '__main__':
BaiduLocalSpider(thread_count=1).start()
CycleSpider
周期爬虫,使用 mysql 创建任务表,并在爬虫内部获取进行抓取
个人认为,任务需求是多样化的,所以任务表的 task 字段是一个字符串,并且获取的任务是全表字段,这样就可以自定义部分表字段,实现更多需求
注意:
- 继承周期爬虫的【同时需要继承 分布式 或 本地爬虫】才可以执行
- 手动导入中间件:palp.middleware.CycleSpiderRecordMiddleware(不需要 record 表,不需要传递 task_id 时框架设置已爬取,可以不要,自己维护就行了)
- 记录中间件会:自动修改失败、成功的任务状态(request 传递 task_id 时,或自己调用)
- 记录中间件会:自动在记录表中添加、修改任务执行情况
含有以下方法:
方法 | 说明 |
---|---|
initialize_all_task_states | 重置所有任务状态为 0 |
get_tasks | 根据指定状态获取任务 |
get_tasks_state0 | 获取状态为 0 的任务 |
get_tasks_state2 | 获取状态为 2 的任务 |
set_task_state | 设置指定任务的状态 |
set_task_state_running | 设置指定任务状态为 1 |
set_task_state_done | 设置指定任务状态为 2 |
set_task_state_failed | 设置指定任务状态为 3 |
create_mysql_table | 创建任务表、记录表 |
check_mysql_table_exists | 检查表是否存在 |
insert_tasks | 插入任务(字符串或字符串列表) |
insert_task_record_start | 创建记录表的初始记录 |
update_task_record | 更新记录表任务处理量 |
update_task_record_end | 更新当前爬虫结束 |
delete_task | 删除任务 |
创建
palp create -s baidu 3
设置
注意:索引是顺序,应该将该中间件放在最后一个
# 启用则修改 task 表中对应任务的失败、生成状态
REQUEST_MIDDLEWARE = {
1: "palp.middleware.CycleSpiderRecordMiddleware",
}
# 启用则生成、汇总 record 表单批次爬取记录
SPIDER_MIDDLEWARE = {
1: 'palp.middleware.CycleSpiderRecordMiddleware',
}
结构
import palp
class CycleSpider(palp.LocalSpider, palp.CycleSpider):
spider_name = "baidu" # 自定义的名字
spider_domains = [] # 允许通过的域名,默认不限制
spider_settings = None # 字典形式或导入形式的设置
spider_table_task_name = f'palp_cycle_task_{spider_name}' # 任务表
spider_table_record_name = f'palp_cycle_record_{spider_name}' # 记录表
def start_requests(self) -> None:
self.initialize_all_task_states() # 重置所有任务状态为 0
# 获取任务状态为 0 的任务
for task in self.get_tasks_state0():
print(task)
yield palp.RequestGet(url=task['task'], task_id=task['id'])
def parse(self, request, response) -> None:
print(response)
if __name__ == '__main__':
CycleSpider.create_mysql_table() # 快捷创建表
CycleSpider.insert_tasks(['https://www.baidu.com', 'https://www.jd.com']) # 快捷插入任务
CycleSpider().start()
task_id
注意到这里传递了 task_id,那么什么时候传递 task_id?
- 首先:task_id 代表 task 的 id,Request 中传递则代表当当前请求成功时 == 任务执行成功,框架自带更新状态
- task 字段就是一个 url,请求成功视为执行成功
- task 字段不是 url,那么就不需要 task_id 字段,只有在你觉得这个 task 执行结束时调用以下代码(比如 pipeline 收到指定数据)
spider.set_task_state_done(task_id=xxx)
spider.set_task_state_failed(task_id=xxx)
CycleSpiderRecordMiddleware
该中间件会记录爬虫的执行状态
- spider_start: 启动时创建记录表初始记录(record 表)
- request_failed: 传递 task_id 时,记录失败的请求( task 表)
- request_close: 传递 task_id 时,记录成功的请求( task 表)
- request_record: spider 结束时,更新 record 表,汇总结果
当你想一直更新 record 表,那么可以自己去写实现,只需调用 CycleSpider 的方法即可
JumpSpider
这是用来解决频繁的登录、验证的,避免逻辑混乱
该 spider 和其余 spider 没有关系,不走任何中间件,是完全独立的
该 spider 含有以下方法
- spider_start:执行开始时的方法
- spider_close:执行结束时的方法
- jump_out:自动合并 cookie、meta(可根据需求重写)
注意:
- spider 运行错误时,将会将输入请求放入 redis 失败队列(分布式时,需开启对应设置)
创建
palp create baidu 4
使用
JumpSpider 内注意判断状态
不正常的时候可以 yield 原始请求,达到循环的目的,直到达到我们的需求,再去执行正常代码
【示例】
yield RequestGet(
url='xx',
jump_spider='xxx', # 导入 JumpSpider(不需要实例化)
jump_spider_kwargs={}, # 导入的参数 JumpSpider 中 self.xxx 访问
jump_request_middleware='xxx', # 导入请求中间件(不需要实例化)
callback='xxx'
)
启动爬虫
爬虫内启动
BaiduSpider(thread_count=1).start() # 启动可选参数看源码
启动文件启动
注意:是进程形式启动,推荐直接使用 start.py 即可,可同时启动多个爬虫
参数介绍
- spider_name:爬虫名(spider 内一致)
- count:启动多少个实例(可实现单机分布式,就是进程启动多少个)
- **kwargs:爬虫启动参数,同 spider 的启动参数
【示例】
from palp import start_spider
def main():
start_spider.execute(spider_name='xxx', count=1)
if __name__ == '__main__':
main()