17--Scrapy03:分页、模拟登录与中间件
Scrapy03--分页、处理cookie与中间件
一、分页(多页)抓取思路
### 不涉及scrapy,所有网站分页的抓取思路
### 1.正常的 普通分页
分页表现:
上一页 1,2,3,4,5,6 下一页
# 情况1: 页面源代码中 有分页的url
解决方案:
1.访问第一页 ---> 提取下一页的url, 访问下一页的url ---> 依次下一页
2.直接观察总计多少页, 去观察每一页url的变化
用程序来实现,完成url的变化
# 情况2:页面源代码中 没有分页的url
解决方案:
去抓包里分析规律
多点几次 下一页 或 第几页,对比看看 差异在哪
# 有可能体现在url上, 也有可能体现在请求的参数上
再用程序来处理
### 2.不正常的分页
# 情况1: 点击加载更多 点击一下,出来一堆分页的url
# eg: 微博的逻辑 链式请求
第一页 ---> 结果,有个参数 ---> 第二页使用 该请求参数值
url = xxxx;
params = {
参数1,
参数2,
参数3,
参数4,
}
for i in range(20):
resp = requests.get(url, params)
1.存本页的数据 resp.text
2.提取下一页的关键参数 = 提取(resp.text)
3.动态部分修改 下次请求的 关键参数
params[关键参数名] = 关键参数
# 情况2: 滚动刷新
两者的解决方案:
不正常的分页,都只是体现在 触动下一页的 事件(点击按钮 或 滚动鼠标 )不一样而已
网络--请求类型 基本都是 Ajax 异步加载, 有些是 js类型
抓包,分析规律 (有可能体现在url上, 也有可能体现在参数上)
# 总结
抓包就是 见招拆招
切忌,死磕每一行代码
二、Scrapy处理cookie
2.0 登录处理的原理
### 登陆之后的效果
# 1.常规登陆的逻辑
网站会在cookie中写入登陆信息
网站直接在登陆成功之后,在返回的响应头,里面带着 "set-cookie"
后续的请求会在请求头中. 就可发现cookie的内容
# 处理:
用session来自动维护响应头的set-cookie
session = requests.session()
# 2.ajax的登陆
通过ajax登陆后
从浏览器中,在返回的响应头,可能没有 "set-cookie"
但在后续的访问中. 发现有cookie,并且cookie有很明显的登陆后的字样(登录之后的token/id 等)
90%可能性是: cookie是通过javascript脚本语言,动态设置的
# 处理:
这种情况,session就不能自动维护了. 需要通过程序手工,去完成cookie的拼接
eg: ajax登陆成功后,返回的json数据
# {username: alex, usertoken: 10086}
需要按照js里的处理过程,用python实现一样的逻辑 # JS逆向
# 3.依然是ajax请求. 也没有响应头,也是js脚本设置
和2的区别是:
它不会把登陆信息放在cookie中
而是把登陆信息放在storage里 # 新型的网页基本上都是
每次请求,都从storage拿出来登陆信息(基本都带着 加解密),放在请求参数里去访问
# 处理:必须JS逆向
有一个统一的解决方案. 去找公共拦截器
在requests中,处理cookie主要有两个方案
第一个方案:从浏览器里直接把cookie搞出来,贴到heades里 这种方案,简单粗暴
第二个方案:是走正常的登录流程. 通过session来记录请求过程中的cookie
scrapy中如何处理cookie? 其实也是这两个方案
案例网址:https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919
这个url必须要登录后,才能访问(用户书架),对于该网页而言,就必须要用到cookie了
首先:创建项目,建立爬虫
import scrapy
from scrapy import Request, FormRequest
class LoginSpider(scrapy.Spider):
name = 'login'
allowed_domains = ['17k.com']
start_urls = ['https://user.17k.com/ck/author/shelf?page=1&appKey=2406394919']
def parse(self, response):
print(response.text)
此时运行时,显示的是该用户还未登录。不论是哪个方案,在请求到start_urls里面的url之前,必须得获取到cookie
但默认情况下, scrapy会自动完成起始request的创建。此时, 需要自己去组装第一个请求.
需要在爬虫中重写start_requests()
方法. 该方法负责起始request的组装工作.
2.1 start_requests()
源码
# 以下是scrapy中 start_requests() 源码
是在 引擎从 爬虫类属性中 提取到start_urls时,组装成起始的请求对象
def start_requests(self):
cls = self.__class__
if not self.start_urls and hasattr(self, 'start_url'):
raise AttributeError(
"Crawling could not start: 'start_urls' not found "
"or empty (but found 'start_url' attribute instead, "
"did you miss an 's'?)")
if method_is_overridden(cls, Spider, 'make_requests_from_url'):
warnings.warn(
"Spider.make_requests_from_url method is deprecated; it "
"won't be called in future Scrapy releases. Please "
"override Spider.start_requests method instead (see %s.%s)." % (
cls.__module__, cls.__name__
),
)
for url in self.start_urls:
yield self.make_requests_from_url(url)
else:
for url in self.start_urls:
# 核心就一句话,组建一个Request对象
yield Request(url, dont_filter=True)
自定义start_requests()
试试
def start_requests(self):
print("我是万恶之源")
yield Request(
url=LoginSpider.start_urls[0],
callback=self.parse
)
2.2 方案1: settings.py 配置cookie值
方案:在settings中.有一个配置项: DEFAULT_REQUEST_HEADERS
,可配置 默认的请求头信息
但注意:需要在settings中,把COOKIES_ENABLED
设置成False
。 否则,在下载器中间件中,会被干掉
# 默认:scrapy是用session进行请求的,在cookie中间件(初始化)时,会把默认请求头中的cookie干掉
# 默认的cookie中间件 是否可用 False之后,scrapy就帮不自动维护cookie了,默认请求头中的cookie才会生效
COOKIES_ENABLED = False
# 设置默认请求头
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'Cookie': 'xxxxxx',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36"
}
# 优缺点:
优点:配置简单
缺点:所有请求都是固定的一个默认请求头了,scrapy也不在自动维护cookie了
2.2 方案2: Request对象的cookies参数
方案:spider中,重写start_requests(),在构建起始页请求request对象时,手动加上 cookies参数
# 前提:
网站前台自己输入账号密码登录后,直接从浏览器复制cookies
def start_requests(self):
cookies = "GUID=bbb5f65a-2fa2-40a0-ac87-49840eae4ad1; c_channel=0; c_csc=web; Hm_lvt_9793f42b498361373512340937deb2a0=1627572532,1627711457,1627898858,1628144975; accessToken=avatarUrl%3Dhttps%253A%252F%252Fcdn.static.17k.com%252Fuser%252Favatar%252F16%252F16%252F64%252F75836416.jpg-88x88%253Fv%253D1610625030000%26id%3D75836416%26nickname%3D%25E5%25AD%25A4%25E9%25AD%2582%25E9%2587%258E%25E9%25AC%25BCsb%26e%3D1643697376%26s%3D73f8877e452e744c; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2275836416%22%2C%22%24device_id%22%3A%2217700ba9c71257-035a42ce449776-326d7006-2073600-17700ba9c728de%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%2C%22first_id%22%3A%22bbb5f65a-2fa2-40a0-ac87-49840eae4ad1%22%7D; Hm_lpvt_9793f42b498361373512340937deb2a0=1628145672"
cookie_dic = {}
for c in cookies.split("; "):
k, v = c.split("=", 1)
cookie_dic[k] = v
yield Request(
url=LoginSpider.start_urls[0],
cookies=cookie_dic, # 通过Request对象的 cookies参数 字典形式
callback=self.parse
)
# 注意:
该方案和原来的requests几乎一模一样. 需要注意的是: cookie需要通过cookies参数进行传递!
# 优缺点:
优点:操作还算简单,起始页配置cookie后,后续的请求 也不用手动添加cookie了 (scrapy继续自动维护cookie)
2.4 方案3: post请求 手动登录
方案:先 手动处理登录流程,登录完成后(scrapy自动维护cookie),再去执行访问起始页
# 1.先在start_request()中,用post方式,登录访问登录页面的url
scrapy 底层中,使用的是类似session的东西,会自动保持会话
# 2.再在parse()中,封装真正起始页 start_urls的请求对象
start_request() 请求完成后,返回触发parse()函数
def start_requests(self):
# 手动处理 登录流程
data = {
"loginName": "18614075987",
"password": "q6035945"
}
login_url = "https://passport.17k.com/ck/user/login"
### 1 Request对象 发送post请求 数据放在body中,且需要手动拼接处理
body_data = '' # 数据类型是form-data:"loginName=18614075987&password=q6035945"
# 和urlencoded 形式一样 本质都是二进制的字符串
for k, v in data.items():
body_data += k + '=' + v + '&'
body_data = body_data[:-1] # 去除最后一次的'&'
# 借助模块 urllib 进行自动拼接
from urllib.parse import urlencode
body_data = urlencode(data)
yield Request(
url=login_url,
method="post",
body=body_data,
callback=self.parse
)
### 2 FormRequest对象 发送post请求
yield FormRequest(
url=login_url,
formdata=data,
callback=self.parse
)
def parse(self, response):
# 得到登录的响应结果后,直接请求到默认的start_urls
yield Request(
url=LoginSpider.start_urls[0],
callback=self.parse_detail
)
def parse_detail(self, resp):
print(resp.text)
-
**注意:请求有两个方案 **
Scrapy.Request(url=url, method='post', body=数据)
Scarpy.FormRequest(url=url, formdata=数据)
====> 推荐
区别: 方式1的数据只能是字符串,就很难受.。所以推荐用第二种,参数可以直接字典形式
三、Scrapy的中间件
中间件的作用:负责处理引擎和爬虫、引擎和下载器之间的请求和响应
主要是可以对request、response做预处理,为后面的操作做好充足的准备工作。
Scrapy中两种中间件,分别是下载器中间件、爬虫中间件
3.1 DownloaderMiddleware
下载中间件,是介于引擎和下载器之间。引擎在获取到request对象后,会交给下载器去下载,在这之间,可以设置下载中间件。
下载中间件的执行流程:
引擎传递request ---> 中间件1(process_request) ---> 中间件2(process_request) .....---> 下载器
引擎拿到response <--- 中间件1(process_response) <--- 中间件2(process_response) ..... <--- 下载器
class MidDownloaderMiddleware1:
"""
scrapy引擎使用该类(中间件的类)时,是需要 该类的对象
但引擎创建该类对象,不是常规的 类名()
而是通过 调用该类的 from_crawler方法,创建该类的对象。
引擎里 大概就是:
from settings import *
for i in settings.里面配置的 中间件类名列表的 变量名
obj = i(热插拔式 中间类名).from_crawler()
"""
@classmethod # 类方法,作用于类本身
def from_crawler(cls, crawler): # 参数是 中间件类名 和 引擎对象
# This method is used by Scrapy to create your spiders.
s = cls()
"""
# 这里很关键哦. 原理是 通过信号机制,设置spider开始 和结束后 的操作
在爬虫开始的时候. 执行spider_opened
在爬虫结束的时候. 执行spider_closed
引擎对象.信号.连接 将该中间件类的 spider_opened方法,连接到 爬虫spider对象 执行前后的信号
"""
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
return s
def process_request(self, request, spider): # 请求 从引擎-->下载器 触发
print("process_request", "ware1")
return None
def process_response(self, request, response, spider): # 响应 从下载器-->引擎 触发
print("process_response", "ware1")
return response
def process_exception(self, request, exception, spider): # 当下载器或process_request()方法,抛出异常时触发
print("process_exception", "ware1")
pass
def spider_opened(self, spider):
print("定义 爬虫spider开始前的 操作")
def spider_closed(self, spider):
self.web.close()
print("定义 爬虫spider结束后的 操作")
class MidDownloaderMiddleware2:
def process_request(self, request, spider):
print("process_request", "ware2")
return None
def process_response(self, request, response, spider):
print("process_response", "ware2")
return response
def process_exception(self, request, exception, spider):
print("process_exception", "ware2")
pass
设置中间件
DOWNLOADER_MIDDLEWARES = {
# 'mid.middlewares.MidDownloaderMiddleware': 542,
'mid.middlewares.MidDownloaderMiddleware1': 543,
'mid.middlewares.MidDownloaderMiddleware2': 544,
}
优先级参考管道:数字越小,距离引擎 越近 处理请求的越先被执行,处理响应的则相反
3.1.0 下载中间件 方法的返回值
难点
-
process_request(request, spider):
在每个请求到达下载器之前 调用return None
不拦截,请求继续,向后传递给权重低的中间件 或 下载器return request对象
请求被拦截,并将一个新的请求返回(会被引擎放到调度器,等待下一次被调度执行),后续中间件以及下载器收不到本次请求return response对象
请求被拦截,并以响应对象返回(会被引擎放到spider中,解析数据),但下载器将获取不到本次请求return IgnoreRequest 异常
触发process_exception的执行,若没有方法处理该异常 ,则该请求就直接被忽略了, 也不会记录错误日志用途:修改请求头、修改cookie、添加代理、添加selenium
-
proccess_response(request, response, spider):
每个请求从下载器返回出来 调用return request对象
响应被拦截,将返回内容直接回馈给调度器(通过引擎),后续下载中间件的process_response()
接收不到响应内容return response对象
响应不拦截,通过引擎,将响应内容继续传递给其他下载中间件,直到引擎返回给spiderreturn IgnoreRequest 异常
该请求被忽略了且不做记录用途:(响应失败时)自动重试、处理302重定向
3.1.1 动态随机设置UA
设置统一的UA很简单,直接在settings里设置即可
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36'
但是这个不够好,希望得到一个随机的UA
http://useragentstring.com/pages/useragentstring.php?name=Chrome
- 首先,在settings.py中,定义好一堆UserAgent
USER_AGENT_LIST = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2919.83 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2866.71 Safari/537.36',
'Mozilla/5.0 (X11; Ubuntu; Linux i686 on x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2820.59 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2762.73 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2656.18 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML like Gecko) Chrome/44.0.2403.155 Safari/537.36',
'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',
'Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36',
]
- 其次,修改中间件
class MyRandomUserAgentMiddleware:
def process_request(self, request, spider):
UA = choice(USER_AGENT_LIST)
request.headers['User-Agent'] = UA # 修改请求头
return None # 不要返回任何东西 就是返回 None,请求继续向后执行
# 拓:fake_useragent模块,可以随机生成user-aget
from fake_useragent import UserAgent
ua = UserAgent()
print(ua.ie) # 随机打印ie浏览器任意版本
print(ua.firefox) # 随机打印firefox浏览器任意版本
print(ua.chrome) # 随机打印chrome浏览器任意版本
def process_response(self, request, response, spider):
return response
def process_exception(self, request, exception, spider):
pass
3.1.2 添加代理proxy
代理问题一直是爬虫工程师很蛋疼的问题,不加容易被检测,加了效率低,免费的可用IP更是凤毛麟角。
采用两个方案,展示scrapy中添加代理的逻辑
-
免费代理
# settings.py中,需手动存储一堆免费的代理 PROXY_LIST = [ ] # middlewars.py class ProxyMiddleware: def process_request(self, request, spider): proxy = choice(PROXY_LIST) request.meta['proxy'] = "https://" + proxy # 设置代理 return None def process_response(self, request, response, spider): return response def process_exception(self, request, exception, spider): print("出错了!") pass
-
收费代理
选择
快代理
,根据自己喜好进行调整建议购买隧道代理,原理就是 浏览器 ---> 隧道(隧道自己根据实际情况,处理加上 不同的IP 去访问) ---> 目标网站
from w3lib.http import basic_auth_header class MoneyProxyMiddleware: def _get_proxy(self): """ # 在我的隧道代理 查看 + API接口 SDK & 代码样例 订单号:912831993520336 隧道ID:t12831993520578 换IP周期:每次请求换IP 隧道host:tps138.kdlapi.com http端口:15818 并发量:5次/s 带宽:3Mb/s(BGP) 状态:有效 隧道用户名:t12831993520578 密码:t72a13xu :return: """ url = "http://tps138.kdlapi.com:15818" # 隧道代理服务器地址 auth = basic_auth_header(username="t12831993520578", password="t72a13xu") return url, auth def process_request(self, request, spider): url, auth = self._get_proxy() request.meta['proxy'] = url request.headers['Proxy-Authorization'] = auth request.headers['Connection'] = 'close' return None def process_response(self, request, response, spider): return response def process_exception(self, request, exception, spider): pass
3.1.3 自动重试
class RetryMiddleware:
def process_request(self, request, spider):
return None
def process_response(self, request, response, spider):
print(response.status, type(response.status))
if response.status != 200:
request.dont_filter = True # 将该请求的参数 不过滤 设为True 因为调度器有自动去重
return request # 重新 返回该请求对象,丢进调度器
return response
def process_exception(self, request, exception, spider):
pass
3.1.4 使用selenium请求数据
- 封装设计思路
### 思路:
Request: 请求 -> 猫
SeleniumRequest: 请求 -> 波斯猫
# 程序里面: 中间件里的,处理请求的逻辑
def process_request(self, 猫, spider):
if 猫 是 波斯猫:
好好养
pass
# 优点: scrapy中 很多类重写,其实就是这个思路 继承 + 多态
设计时:
1.设计:设计多个不同的类,都是继承同一个父类 # 继承
2.处理:根据判断 对象 属于 不同的类,从而进行不同的处理 # 多态
使用时:
只需要 实例化 不同类型的对象,就可以实现不同的功能
- 首先,自定义SeleniumRequest 请求类
# 缘由
使用selenium作为下载器进行下载,按照思路,那么请求应该也是特殊订制的
所以可以重新设计一个请求类,就叫SeleniumRequest
# 自定义的 myrequest.py
from scrapy import Request
class SeleniumRequest(Request):
pass
# 这里面不需要做任何操作,整体还是用它父类的东西,来进行操作
- 其次,完善spider
import scrapy
from boss.myrequest import SeleniumRequest
class BeijingSpider(scrapy.Spider):
name = 'beijing'
allowed_domains = ['zhipin.com']
start_urls = ['https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=']
# 重写 处理起始页 的请求对象
def start_requests(self):
yield SeleniumRequest(
url=BeijingSpider.start_urls[0],
callback=self.parse,
)
def parse(self, resp, **kwargs):
li_list = resp.xpath('//*[@id="main"]/div/div[3]/ul/li')
for li in li_list:
href = li.xpath("./div[1]/div[1]/div[1]/div[1]/div[1]/span[1]/a[1]/@href").extract_first()
name = li.xpath("./div[1]/div[1]/div[1]/div[1]/div[1]/span[1]/a[1]/text()").extract_first()
print(name, href)
print(resp.urljoin(href))
yield SeleniumRequest(
url=resp.urljoin(href),
callback=self.parse_detail,
)
# 下一页.....
def parse_detail(self, resp, **kwargs):
print("招聘人", resp.xpath('//*[@id="main"]/div[3]/div/div[2]/div[1]/h2').extract())
- 然后,修改中间件
from scrapy.http import HtmlResponse
from selenium.webdriver import Chrome
from boss.myrequest import SeleniumRequest
class BossDownloaderMiddleware:
@classmethod # 类方法,作用于类本身
def from_crawler(cls, crawler): # 参数是 中间件类名 和 引擎对象
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
crawler.signals.connect(s.spider_closed, signal=signals.spider_closed)
return s
def process_request(self, request, spider):
# 若当前request对象,是SeleniumRequest类的对象
if isinstance(request, SeleniumRequest):
self.web.get(request.url) # selenium get访问url
time.sleep(3)
page_source = self.web.page_source
# 返回scrapy的response对象(url, body, request, encoding)
return HtmlResponse(url=request.url, encoding='utf-8', request=request, body=page_source)
# 若不是SeleniumRequest类的对象,就是正常的request对象,走默认的 返回 空
return None # 写不写 都无所谓,都是空
def process_response(self, request, response, spider):
return response
def process_exception(self, request, exception, spider):
pass
def spider_opened(self, spider):
self.web = Chrome()
self.web.implicitly_wait(10)
# 完成登录. 拿到cookie. 很容易...
print("创建浏览器")
def spider_closed(self, spider):
self.web.close()
print("关闭浏览器")
- 最后,修改settings
DOWNLOADER_MIDDLEWARES = {
# 放在在所有默认中间件前面 只要是selenium请求,后面所有的中间件都给我停
'boss.middlewares.BossDownloaderMiddleware': 99,
}
3.1.5 使用selenium设置cookie
直接在中间件spider_opened()
里完成登录, 然后在process_request()
中,简单设置一下即可
from scrapy.http import HtmlResponse
from selenium.webdriver import Chrome
class ChaojiyingDownloaderMiddleware:
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_request(self, request, spider):
if not request.cookies: # cookie 为空时,添加cookie
request.cookies = self.cookie
return None
def process_response(self, request, response, spider):
return response
def process_exception(self, request, exception, spider):
pass
def spider_opened(self, spider):
web = Chrome()
web.get("https://www.chaojiying.com/user/login/")
web.find_element_by_xpath('/html/body/div[3]/div/div[3]/div[1]/form/p[1]/input').send_keys("18614075987")
web.find_element_by_xpath('/html/body/div[3]/div/div[3]/div[1]/form/p[2]/input').send_keys('q6035945')
img = web.find_element_by_xpath('/html/body/div[3]/div/div[3]/div[1]/form/div/img')
# 处理验证码
verify_code = self.base64_api("q6035945", "q6035945", img.screenshot_as_base64, 3)
web.find_element_by_xpath('/html/body/div[3]/div/div[3]/div[1]/form/p[3]/input').send_keys(verify_code)
web.find_element_by_xpath('/html/body/div[3]/div/div[3]/div[1]/form/p[4]/input').click()
time.sleep(3)
cookies = web.get_cookies()
self.cookie = {dic['name']:dic['value'] for dic in cookies}
web.close()
def base64_api(self, uname, pwd, b64_img, typeid):
data = {
"username": uname,
"password": pwd,
"typeid": typeid,
"image": b64_img
}
# result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
result = requests.post("http://api.ttshitu.com/predict", json=data).json()
if result['success']:
return result["data"]["result"]
else:
return result["message"]
3.2 SpiderMiddleware(了解)
爬虫中间件:是处于引擎和spider之间的中间件。里面常用的方法有:
class CuowuSpiderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the spider middleware does not modify the
# passed objects.
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_spider_input(self, response, spider):
# 请求被返回, 即将进入到spider时调用
# 要么返回None, 要么报错
print("我是process_spider_input")
return None
def process_spider_output(self, response, result, spider):
# 处理完spider中的数据. 返回数据后. 执行
# 返回值要么是item, 要么是request.
print("我是process_spider_output")
for i in result:
yield i
print("我是process_spider_output")
def process_spider_exception(self, response, exception, spider):
print("process_spider_exception")
# spider中报错 或者, process_spider_input() 方法报错
# 返回None或者Request或者item.
it = ErrorItem()
it['name'] = "exception"
it['url'] = response.url
yield it
def process_start_requests(self, start_requests, spider):
print("process_start_requests")
# 第一次启动爬虫时被调用.
# Must return only requests (not items).
for r in start_requests:
yield r
def spider_opened(self, spider):
pass
items
class ErrorItem(scrapy.Item):
name = scrapy.Field()
url = scrapy.Field()
spider
class BaocuoSpider(scrapy.Spider):
name = 'baocuo'
allowed_domains = ['baidu.com']
start_urls = ['http://www.baidu.com/']
def parse(self, resp, **kwargs):
name = resp.xpath('//title/text()').extract_first()
# print(1/0) # 调整调整这个. 简单琢磨一下即可~~
it = CuowuItem()
it['name'] = name
print(name)
yield it
pipelines
from cuowu.items import ErrorItem
class CuowuPipeline:
def process_item(self, item, spider):
if isinstance(item, ErrorItem):
print("错误", item)
else:
print("没错", item)
return item
目录结构
cuowu
├── cuowu
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ └── baocuo.py
└── scrapy.cfg