网络爬虫 – scrapy进阶(十四)

scrapy进阶

一、构建请求

1. scrapy.Request
2. scrapy.FormRequest
3. scrapy.JsonRequest
4. meta的使用

二、Middlewares

1. Downloader Middlewares
2. 随机替换请求头
3. 随机替换IP
4. 设置Cookie值
5. 超时重试


一、构建请求

1. scrapy.Request

Request 和 Response 对象,用于爬网网站。

Request对象用来描述一个HTTP请求,下面是其构造器方法的参数列表:

Request(
    # 必需参数
    url: str,
    
    # 核心参数
    callback: Optional[Callable] = None,
    method: str = 'GET',
    
    # 请求内容参数
    headers: Optional[dict] = None,
    body: Optional[Union[str, bytes]] = None,
    cookies: Optional[Union[dict, List[dict]]] = None,
    
    # 元数据与控制参数
    meta: Optional[dict] = None,
    priority: int = 0,
    dont_filter: bool = False,
    
    # 编码与错误处理
    encoding: str = 'utf-8',
    errback: Optional[Callable] = None,
    
    # 高级参数
    flags: Optional[list] = None,
    cb_kwargs: Optional[dict] = None
)

必需参数:
url:请求的目标URL(唯一必需参数)
请求行为控制:
method:HTTP方法(默认GET)
headers:请求头字典,dict值可以是字符串(对于单值标头)或列表(对于多值标头)。如果 None作为值传递,则将根本不发送HTTP标头。
body:原始请求体(str/bytes)
cookies:Cookie信息
回调处理:
callback:响应回调函数,将以请求的响应(一旦下载)作为第一个参数调用的函数。有关更多信息,请参见下面的将其他数据传递给回调函数。如果“请求”未指定回调,parse() 则将使用“Spider” 方法。请注意,如果在处理过程中引发异常,则会调用errback。
errback:异常回调函数
cb_kwargs:传递给回调的额外参数
流程控制:
priority:调度优先级(数字越大越优先)
dont_filter:是否跳过去重(默认False)
数据传递:
meta:请求间传递数据的字典,Request.meta属性的初始值。如果给出,则在此参数中传递的字典将被浅表复制。
flags:请求标记列表(用于日志/监控)
编码:
encoding:默认编码格式

import scrapy
from your_project.items import Qd01QuotesItem  # 请替换为实际Item路径


class QuotesSpider(scrapy.Spider):
    name = 'quotes_3'
    allowed_domains = ['quotes.toscrape.com']  # 修正域名
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        # 解析每段引文
        for quote in response.css('.quote'):
            yield Qd01QuotesItem(
                text=quote.css('.text::text').get(),
                author=quote.css('.author::text').get(),
                tags=quote.css('.tag::text').getall()
            )

        # 处理分页
        next_page = response.css('.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

2. scrapy.FormRequest

FormRequest类扩展了请求基类 Request 具有处理HTML表单的功能。

scrapy 框架中封装的 FormRequest 方法可以帮助我们发送post请求
参数:
formdata:是包含HTML表单数据的字典(或可为(键、值)元组),实际上指的就是请求参数,这些数据将被URL编码并分配给请求主体。
callback:回调函数指定,此次请求交给哪个函数做处理。

from scrapy.http import FormRequest


def start_requests(self):
    yield FormRequest(
        url='http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword',
        formdata={
            
            'cname': '',
            'pid': '',
            'keyword': '北京',
            'pageIndex': '1',
            'pageSize': '10',
        },
        callback=self.parse,
    ) 

3. scrapy.JsonRequest

使用JSON负载发送JSON POST请求:

def start_requests(self):
    yield scrapy.http.JsonRequest(
        url='http://www.zfcg.sh.gov.cn/portal/category',
        data={
            
            "pageNo": 1,
            "pageSize": 15,
            "categoryCode": "ZcyAnnouncement1",
            "_t": 1687780627000
        },
        callback=self.parse
    )

4. meta的使用

meta 参数主要用于在两个函数之间传递参数

meta 一次性的,每次创建 request 对象,都会重新创建

meta是一个字典,它的主要作用是用来传递数据的,meta ={‘key1’:value1},如果想在下一个函数中取出 value1, 只需得到上一
个函数的 meta[‘key1’] 即可, 因为meta是随着Request产生时传递的,下一个函数得到的Response对象中就会有meta,即response.meta。请瞧下面的简单例子更好的帮助理解:

import scrapy
from your_project.items import Win4000Item  # 请替换为实际Item路径


class Win4000Spider(scrapy.Spider):
    name = 'win4000'
    allowed_domains = ['win4000.com']
    start_urls = ['http://www.win4000.com/mobile_detail_169064.html']

    def parse(self, response):
        """解析初始页面"""
        item = Win4000Item()
        item['title'] = response.css('h1::text').get()
        item['img_urls'] = response.css('.pic-large::attr(src)').getall()
        
        # 调试输出(正式环境建议使用logging)
        print('Current item:', item)  
        
        # 获取下一页链接
        next_page = response.css('.pic-next-img a::attr(href)').get()
        if next_page:
            yield scrapy.Request(
                url=next_page,
                callback=self.parse_detail,  # 更清晰的回调函数命名
                meta={
            'item': item}  # 传递当前item到下一页
            )

    def parse_detail(self, response):
        """解析详情页数据"""
        item = response.meta['item']  # 直接获取传递的item
        
        # 这里可以继续添加第二页的数据提取逻辑
        # 例如:item['detail'] = response.css(...).get()
        
        yield item  # 最终返回完整item

二、Middlewares

1. Downloader Middlewares

Downloader Middlewares(下载器中间件),位于 scrapy 引擎和下载器之间的一层组件。作用:

引擎将 请求 传递给下载器之前,下载中间件 可以对 请求 进行一系列处理。比如设置请求的User-Agent,设置代理等
在下载器完成将Response传递给引擎之前,下载中间件可以对响应进行一系列处理。

我们主要使用下载中间件处理请求,一般会对请求设置随机的User-Agent,设置随机的代理。目的在于防止爬取网站的反爬虫策略。

如果完全没有中间件,爬虫的流程如下图所示。

使用了中间件以后,爬虫的流程如下图所示。
图片[1] - 网络爬虫 – scrapy进阶(十四) - 宋马
Scrapy 自动生成的这个文件名称为 middlewares.py,名字后面的s 表示复数,说明这个文件里面可以放很多个中间件。可以看到有一个 SpiderMiddleware (爬虫中间件)中间件 和DownloaderMiddleware (下载中间件)中间件

在 middlewares.py 中添加下面一段代码(可以在 下载中间件这个类 里面写,也可以把 爬虫中间件 和 下载中间件 这两个类删了,自己写个 下载中间件的类。推荐自己单写一个类作为下载中间件):

默认下载器中间件代码如下:

class TutorialDownloaderMiddleware(object):
    """
    自定义下载器中间件示例
    不是所有方法都需要定义。如果某个方法未定义,
    Scrapy 会认为该下载器中间件不对相应对象做修改
    """
    
    @classmethod
    def from_crawler(cls, crawler):
        """
        这个方法由 Scrapy 用来创建中间件实例
        """
        s = cls()
        # 连接 spider_opened 信号
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        """
        处理每个经过下载器中间件的请求
        
        参数:
            request (Request object) - 被处理的请求
            spider (Spider object) - 该请求对应的爬虫
        
        必须返回以下之一:
            - None: 继续处理该请求
            - Response 对象: 停止处理请求,直接使用该响应
            - Request 对象: 停止处理请求,使用该新请求
            - 抛出 IgnoreRequest 异常: 将调用 process_exception() 方法
        """
        return None

    def process_response(self, request, response, spider):
        """
        处理下载器返回的响应
        
        参数:
            request (Request object) - 该响应对应的请求
            response (Response object) - 被处理的响应
            spider (Spider object) - 该响应对应的爬虫
        
        必须返回以下之一:
            - Response 对象: 继续处理该响应
            - Request 对象: 停止处理,使用该新请求
            - 抛出 IgnoreRequest 异常
        """
        return response

    def process_exception(self, request, exception, spider):
        """
        处理下载处理器或 process_request() 抛出的异常
        
        参数:
            request (Request object) - 产生异常的请求
            exception (Exception object) - 抛出的异常
            spider (Spider object) - 该请求对应的爬虫
        
        必须返回以下之一:
            - None: 继续处理该异常
            - Response 对象: 停止异常处理链,使用该响应
            - Request 对象: 停止异常处理链,使用该新请求
        """
        pass

    def spider_opened(self, spider):
        """
        Spider 开启时的回调方法
        """
        spider.logger.info('Spider opened: %s' % spider.name)
        

process_request

process_request(self, request, spider)

该方法是下载器中间件类中的一个方法,该方法是每个请求从引擎发送给下载器下载之前都会经过该方法。所以该方法经常用来处理请求头的替换,IP的更改,Cookie等的替换。

参数:

request (Request 对象) – response所对应的request
response (Response 对象) – 被处理的response
spider (Spider 对象) – response所对应的spider

2. 随机替换请求头

有些网站需要用户在访问的时候确认用户采用的是用浏览器来进行访问的,也就是常见的User-Agent信息。在Scrapy中也可以设置相应的请求头信息。

class UserAgentDownloaderMiddleware(object):
    def __init__(self):
        self.fake = Faker('en_US')
        
    def process_request(self, request, spider):
        useragent = self.fake.user_agent()
        request.headers.update({
            "User-Agent": useragent})
        return None

注意:除了编写下载器中间件,还需要激活配置好的中间件才能生效。在settings.py中启用该中间件:

重点:
DOWNLOADER_MIDDLEWARES 设置会与 Scrapy 默认定义的
DOWNLOADER_MIDDLEWARES_BASE设置合并(但不是覆盖), 而后根据顺序(order)进行排序,最后得到启用中间件的有序列表: 第一个中间件是最靠近引擎的,最后一个中间件是最靠近下载器的。

关于如何分配中间件的顺序请查看
DOWNLOADER_MIDDLEWARES_BASE 设置,而后根据想要放置中间件的位置选择一个值。 由于每个中间件执行不同的动作,中间件可能会依赖于之前(或者之后)执行的中间件,因此顺序是很重要的。

faker 是一个虚假数据生成库,可以随机生成各种虚假数据用于测试

import faker
fake = faker.Faker()
fake.name()
Out[4]: ‘John Richardson’
fake.name_male()
Out[5]: ‘Anthony Jackson’
fake.user_agent()
Out[6]: ‘Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/3.1)’

3. 随机替换IP

当使用Scrapy爬虫大规模请求某个网站时,经常会遇到封禁IP的情况。在这种情况下,设置IP代理就非常重要了。在Scrapy中设
置代理IP也很简单,其原理就是在发送请求之前,指定一个可用的IP代理服务器即可。同样,IP的设置也是在下载器中间件里面设置,自定义自己的IP代理中间件。

from faker import Faker

class HttpProxyDownloaderMiddleware:
    """
    随机代理IP中间件
    功能:为每个请求自动添加随机代理IP
    """

    def __init__(self):
        """初始化代理IP池和Faker实例"""
        self.fake = Faker()
        # 实际使用时应替换为你的代理IP池
        self.proxy_pool = [
            "117.57.90.19:31840",
            "183.45.78.32:8080",
            "121.40.210.21:3128",
            # 添加更多代理IP...
        ]

    @classmethod
    def from_crawler(cls, crawler):
        """Scrapy标准初始化方法"""
        middleware = cls()
        crawler.signals.connect(middleware.spider_opened, signal=signals.spider_opened)
        return middleware

    def process_request(self, request, spider):
        """
        为请求添加代理IP
        注意:如果请求已设置proxy,则不再覆盖
        """
        if 'proxy' not in request.meta and self.proxy_pool:
            # 随机选择代理IP(实际项目建议使用更智能的选择策略)
            proxy = self.fake.random_element(self.proxy_pool)
            
            # 注意:根据代理类型添加协议头
            # HTTP代理
            request.meta['proxy'] = f"http://{
              proxy}"
            
            # HTTPS代理(如果需要)
            # request.meta['proxy'] = f"https://{proxy}"
            
            spider.logger.debug(f"Using proxy: {
              proxy}")

    def spider_opened(self, spider):
        spider.logger.info(f"Proxy middleware activated for {
              spider.name}")

注意:使用前需要在settings.py中配置:

4. 设置Cookie值

有时候我们想要爬取的数据可以需要登录才能看到,这时候我们就需要登录网页。登陆后的网页一般都会在本地保存该网页的登录信息Cookies在本地。只要获取该Cookie,那么在以后跳转到其他网页的时候,只需要携带该Cookie即可。

class CookieDownloaderMiddleware:
    """
    Cookie管理中间件
    功能:自动为请求添加登录态Cookie
    适用于需要维持会话状态的爬虫
    """

    def __init__(self):
        """初始化Cookie配置"""
        # 实际项目中建议从以下方式获取Cookie:
        # 1. 配置文件
        # 2. 数据库
        # 3. 实时登录获取
        self.cookies = {
            
            'example_cookie_name': 'your_cookie_value',
            'sessionid': 'abc123xyz456',
            # 添加更多需要的Cookie...
        }

    @classmethod
    def from_crawler(cls, crawler):
        """Scrapy标准初始化方法"""
        middleware = cls()
        crawler.signals.connect(middleware.spider_opened, signal=signals.spider_opened)
        return middleware

    def process_request(self, request, spider):
        """
        为请求添加Cookie
        策略:
        1. 如果请求已有Cookie头,则保留原有Cookie并合并新Cookie
        2. 如果spider定义了custom_cookies属性,优先使用
        """
        # 优先使用spider级别定义的Cookie
        cookies = getattr(spider, 'custom_cookies', None) or self.cookies
        
        if cookies:
            # 合并已有Cookie(request.headers是字节字符串需要解码)
            existing_cookies = {
            }
            if b'Cookie' in request.headers:
                existing_cookies = {
            
                    k.strip(): v.strip()
                    for k, v in (
                        cookie.split('=') 
                        for cookie in request.headers[b'Cookie'].decode().split(';')
                    )
                }
            
            # 更新Cookie(新值覆盖旧值)
            merged_cookies = {
            **existing_cookies, **cookies}
            
            # 构造Cookie字符串
            cookie_str = '; '.join(f"{
              k}={
              v}" for k, v in merged_cookies.items())
            
            # 设置到请求头(Scrapy会自动编码为字节)
            request.headers['Cookie'] = cookie_str
            
            spider.logger.debug(f"Set cookies: {
              cookie_str}")

    def spider_opened(self, spider):
        spider.logger.info(f"Cookie middleware activated for {
              spider.name}")

注意: 需要激活Cookie中间件。

5. 超时重试

可以直接查看scrapy的源码,在setting文件夹下的default_settings.py 文件里面可以找到超时重试的配置选项。

RETRY_ENABLED = True # 是否开启超时重试
RETRY_TIMES = 2 # initial response + 2 retries = 3
requests 重试次数
RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524,408, 429] # 重试的状态码
DOWNLOAD_TIMEOUT = 1 # 设置超时的时间


以上就是本章节的内容,感谢大家支持,喜欢的话,已关注、点赞+收藏,下期继续更新相关内容!!

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容