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)
  • **注意:请求有两个方案 **

    1. Scrapy.Request(url=url, method='post', body=数据)
    2. 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对象 响应不拦截,通过引擎,将响应内容继续传递给其他下载中间件,直到引擎返回给spider

    return 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
posted @ 2024-04-18 01:25  Edmond辉仔  阅读(136)  评论(0编辑  收藏  举报