064-真正解决scrapy自动将header请求头大写问题

1,403 阅读2分钟

这是坚持技术写作计划(含翻译)的第64篇,定个小目标999,每周最少2篇。


本文主要讲解如何真正解决scrapy将header请求头自动大写(str.title())的问题

背景

搞了个小爬虫,命名参数都正常,但是被模目标网站识别了,用requests又都正常,问题出在scrapy没跑了

分析过程

用到的工具

  • fiddler , charles , wireshark 任选一个抓包工具就行
  • beyondcompare 等比对工具


分别用request和scrapy请求目标网站,url,参数,form等都用一样的数据(排除类似随机数,时间戳,rsa非对称加密等导致的数据不一致的问题)

以fiddler为例,点开抓包数据,选择Raw选项卡,复制到比对工具里,真实用的过程中,
image.png
image.png
找到问题了,scrapy自动将header转换成大写了,看了下scrapy的源码, 问题出在
github.com/scrapy/scra…key.title()

    def normkey(self, key):
        """Normalize key to bytes"""
        return self._tobytes(key.title())

开始以为解决起来比较简单,用了网上的一些办法,还是不行。后来又研究了下源码,结合网上的方案,终于搞定了

新建Headers.py,复制自 github.com/scrapy/scra… ,只改第15行将 self._tobytes(key.title())改成self._tobytes(key)

from w3lib.http import headers_dict_to_raw
from scrapy.utils.datatypes import CaselessDict
from scrapy.utils.python import to_unicode


class Headers(CaselessDict):
    """Case insensitive http headers dictionary"""

    def __init__(self, seq=None, encoding='utf-8'):
        self.encoding = encoding
        super().__init__(seq)

    def normkey(self, key):
        """Normalize key to bytes"""
        return self._tobytes(key)

    def normvalue(self, value):
        """Normalize values to bytes"""
        if value is None:
            value = []
        elif isinstance(value, (str, bytes)):
            value = [value]
        elif not hasattr(value, '__iter__'):
            value = [value]

        return [self._tobytes(x) for x in value]

    def _tobytes(self, x):
        if isinstance(x, bytes):
            return x
        elif isinstance(x, str):
            return x.encode(self.encoding)
        elif isinstance(x, int):
            return str(x).encode(self.encoding)
        else:
            raise TypeError(f'Unsupported value type: {type(x)}')

    def __getitem__(self, key):
        try:
            return super().__getitem__(key)[-1]
        except IndexError:
            return None

    def get(self, key, def_val=None):
        try:
            return super().get(key, def_val)[-1]
        except IndexError:
            return None

    def getlist(self, key, def_val=None):
        try:
            return super().__getitem__(key)
        except KeyError:
            if def_val is not None:
                return self.normvalue(def_val)
            return []

    def setlist(self, key, list_):
        self[key] = list_

    def setlistdefault(self, key, default_list=()):
        return self.setdefault(key, default_list)

    def appendlist(self, key, value):
        lst = self.getlist(key)
        lst.extend(self.normvalue(value))
        self[key] = lst

    def items(self):
        return ((k, self.getlist(k)) for k in self.keys())

    def values(self):
        return [self[k] for k in self.keys()]

    def to_string(self):
        return headers_dict_to_raw(self)

    def to_unicode_dict(self):
        """ Return headers as a CaselessDict with unicode keys
        and unicode values. Multiple values are joined with ','.
        """
        return CaselessDict(
            (to_unicode(key, encoding=self.encoding),
             to_unicode(b','.join(value), encoding=self.encoding))
            for key, value in self.items())

    def __copy__(self):
        return self.__class__(self)

    copy = __copy__

修改scrapy的settings.py文件,参考 twisted 的源码 github.com/twisted/twi…

# 忽略其他
from twisted.web.http_headers import Headers as TwistedHeaders

TwistedHeaders._caseMappings.update({
    b"aa": b"aa",
    b"aa-aa": b"aa-aa",
    b"aa-bb": b"AA-BB",
    b"aa-cc": b"AA-cc",
})

# 清空默认header
DEFAULT_REQUEST_HEADERS = {
}

修改使用代码

from your.package import Headers

headers = {
    "aa": "a",
    "aa-aa": "aa-aa",
    "aa-bb": "AA-BB",
    "aa-cc": "AA-cc",
    # 千万别用 AA-BB或者AA-cc这样的,就是全部用小写,通过修改TwistedHeaders._caseMappings.update的映射来实现就行了
}

yield scrapy.FormRequest(url=url + "?" + parse.urlencode(params, quote_via=parse.quote),
             formdata=body, dont_filter=True, errback=self.error,headers=Headers(headers),

image.png

招聘小广告


山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。
长期招聘,Java程序员,大数据工程师,运维工程师,前端工程师。

参考资料