Scrapy下载中间件

反反爬虫相关机制

(有些网站使用不同程度的复杂性规则防止爬虫访问,绕过这些规则是困难和复杂的,有时可能需要特殊的设置)

通常反爬措施

1. 基于请求头

动态设置User-Agent(随机切换User-Agent,模拟不同用户的浏览器信息)

2. 基于cookie的反爬

  • 禁用Cookies(前提是爬取的网站不需要cookies参数)

  • (cookie池,文件存储,数据库存储)

  • (如何获取cookies,如何验证cookie,如何进行模拟登陆)可以使用request,手动添加,使用selenium

3. 基于IP

  • 代理:代理的原理?,付费代理,免费代理?代理池

4. 基于动态加载的网页

  • ajax
  • js
  • jq
  • (使用selenium)
  • 无头浏览器,有头浏览器,selenium的方法?

5. 数据加密?(一般会写在js代码里)

  • app
  • web网页
  • 使用selenium

针对于反爬手段

方案一

使用 Crawlera(专用于爬虫的代理组件),正确配置和设置下载中间件后,项目所有的request都是通过crawlera发出。 官方网站:https://scrapinghub.com/crawlera 参考网站:https://www.aliyun.com/jiaocheng/481939.html

方式二

自定义下载中间件(Downloader Middlewares)

下载中间件是处于引擎(crawler.engine)和下载器(crawler.engine.download())之间的一层组件,可以用来修改Request和Response。

  • 当引擎传递请求(Request)给下载器的过程中,下载中间件可以对请求进行处理 (例如增加http header信息(User-Agent),增加proxy代理等);

  • 在下载器完成http请求,传递响应给引擎的过程中, 下载中间件可以对响应进行处理

要激活下载器中间件组件,将其加入到 DOWNLOADER_MIDDLEWARES 设置中。 该设置是一个字典(dict),键为中间件类的路径,值为其中间件的优先级。值越低,代表优先级越高。

自定义中间键需要了解middlewares.py下载中间件相关方法

每个中间件组件是一个定义了以下一个或多个方法

class DownloadwareDownloaderMiddleware(object):
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
    从settings中获取值
        # 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):
        所有的request请求在交给下载器之前都会经过这个
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        return None

    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.
        所有的响应结果会经过这个方法

        # Must either;
        # - return a Response object 返回是个相应结果
        # - return a Request object 会把request放在调度器进行调度
        # - or raise IgnoreRequest 异常
        return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.
        处理异常错误

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

from_crawler(cls, crawler)

如果存在该函数,from_crawler会被调用使用crawler来创建中间器对象,必须返回一个中间器对象,通过这种方式,可以访问到crawler的所有核心部件,如settings、signals等

def process_request(self, request, spider)

当每个request对象通过下载中间件时该方法被调用。

process_request() 必须返回以下其中之一:
1. None

  • 如果返回 None: Scrapy将继续处理request,执 行其他的中间件的相应方法。

2. Response 对象

  • 如果返回 Response 对象: Scrapy不会再调用任 何其他的中间件的 process_request() 或相应地下 载函数; 直接返回这个response对象。 已激活的中间件的 process_response()方法则会在 每个 response 返回时被调用。

3. Request 对象

  • 如果返回 Request 对象,Scrapy则停止调用 其他中间件的process_request方法,并重新将返回的 request对象放置到调度器等待下载。

4. IgnoreRequest异常

  • 如果返回raise IgnoreRequest 异常: 下载中间件的 process_exception() 方法会被用。 如果没有捕获该异常, 则request发情请求时设置的 errback(Request.errback)方法会被调用。如果也 没有设置异常回调,则该异常被忽略且不记录。

process_request()有两个参数:

  • request (Request 对象) – 处理的request
  • spider (Spider 对象) – 该request对应的spider

process_response(self, request, response, spider)

当下载器完成http请求,传递Response给引擎的时调用

优先级越高的中间件,越晚被调用,与process_request()相反

process_response() 必须返回以下其中之一:

1. Response 对象

如果返回 Request: 更低优先级的下载中间件的 process_response方法不会继续调用,该Request会被 重新放到调度器任务队列中等待调度,相当于一个新的 Request。
(scrapy会继续调用其他中间件的process_response方法)
2. Request 对象
如果返回 Response 对象,更低优先级的下载中间 件的process_response方法会被继续调用对Response对象进行处理
(停止中间器调用,将其放置到调度器待调度下载)
3. IgnoreRequest异常
如果抛出 IgnoreRequest 异常,则调用request 设置的errback(Request.errback)函数。 如果异常没有 被处理,则该异常被忽略且不记录。

(会被调用来处理函数,如果没有处理,它将会被忽略且不会写进日志)

process_response()有三个参数:

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

process_exception(request, exception, spider)

当process_exception()和process_request()抛出异常时会被调用,应该返回以下对象:None/Response对象/Request对象;

  • 如果返回None:scrapy会继续调用其他中间件的process_exception();

  • 如果返回Response对象:中间件链的process_response()开始启动,不会继续调用其他中间件的process_exception();

  • 如果返回Request对象:停止中间器的process_exception()方法调用,将其放置到调度器待调度下载。


User-Agent

class UserAgentDown(object):  
    def process_request(self,request,spider):  
        from fake_useragent import UserAgent
        userAgent = UserAgent()
        #引入第三方
        random_ua = userAgent.random
        if random_ua:
            print('经过了下载中间件',random_ua)
            request.headers["User-Agent"] = random_ua
---------------------------------------------------------------
USER_AGENTS = [
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
]


class UserAgentDown(object):
    def __init__(self,User_Agents):
         self.User_agents = User_Agents
    @classmethod
    def from_crawler(cls,crawler):
        #在settings中提前设置好
         User_Agent = crawler.settings['USER_AGENTS']
         return cls(User_Agent)
    import random
    #spider.settings和crawler.settings结果一样
    #User_Agent = spider.settings['USERAGENT']
    random_ua = random.choice(User_Agent)
    if random_ua:
        print('经过了下载中间件',random_ua)
        request.headers["User-Agent"] = random_ua

代理

为什么HTTP代理要使用base64编码:

HTTP代理的原理很简单,就是通过HTTP协议与代理服务器建立连接,协议信令中包含要连接到的远程主机的IP和端口号,如果有需要身份验证的话还需要加上授权信息,服务器收到信令后首先进行身份验证,通过后便与远程主机建立连接,连接成功之后会返回给客户端200,表示验证通过,就这么简单,下面是具体的信令格式:


我们在settings.py中

PROXIES = [
    {"ip":"127.0.0.1:6379","pwd":"ljk123456"},
    {"ip":"127.0.0.1:6379","pwd":None},
    {"ip":"127.0.0.1:6379","pwd":None},
    {"ip":"127.0.0.1:6379","pwd":None},
]
------------------------------------------------------
class ProxyDownloadMiddlerware(object):
    def process_request(self,request,spider):
        proxies = spider.settings['PROXIES']
        import random
        proxy_rm = random.choice(proxies)
        if proxy_rm['pwd']:
            #有账号密码的代理
            #对账号密码进行base64编码
            import base64
            base64_pwd = base64.b64encode(proxy_rm['pwd'].encode('utf-8')).decode('utf-8')

            # 对应到代理服务器的信令格式里
            request.headers['Proxy-Authorization'] = 'Basic ' + base64_pwd
            #设置ip
            request.meta['proxy'] = proxy_rm['ip']
        else:
            request.meta['proxy'] = proxy_rm['ip']

Cookie

同样现在settings.py中设置好cookie

class CookiesDownloadMiddlerware(object):
    def process_request(self,request,spider):
        Cookies = spider.settings['COOKIES']
        import random
        cookie_rm = random.choice(Cookies)
        if cookie_rm:
            request.cookies = cookie_rm

srapy从不支持动态加载

因此

4. 设置selenium中间件。

class SeleniumDownloadMiddlerware(object):
def __init__(self):
    self.driver = webdriver.Firefox(
    executable_path='E://Firefox/geckodriver'
    )
    # 设置超时时间
    self.driver.set_page_load_timeout(10)
def process_request(self,request,spider):
    if spider.name == 'test':

    #获取url
        url = request.url
        if url:
            try:
                self.driver.get(url)             
                page = self.driver.page_source
                if page:
                    return HtmlResponse(url=url,status=200,body=page.encode('utf-8'),request=request)
            except TimeoutException as err:
                print("请求超时",url)
                return HtmlResponse(url=url, status=408, body=b"", request=request)

会发现selenium的浏览器一直处于打开状态

在scrapy.Spider中的

def _set_crawler(self, crawler):
self.crawler = crawler
self.settings = crawler.settings
#监控爬虫结束
crawler.signals.connect(self.close, signals.spider_closed)

改进之后

爬虫文件
class TestDemoSpider(scrapy.Spider):
    name = 'test_demo'
    allowed_domains = ['baidu.com']
    start_urls = ['http://www.baidu.com/']

    driver = webdriver.Firefox(
        executable_path='E://Firefox/geckodriver'
    )
    # 设置超时时间
    driver.set_page_load_timeout(10)
------------------------------------------------------

class SeleniumDownloadMiddlerware(object):
    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        # 信号量链接
        crawler.signals.connect(s.close, signal=signals.spider_closed)
        return s
    def close(self,spider):
        #driver就写在spider中

        spider.driver.close()
    def process_request(self,request,spider):
        if spider.name == 'test':

        #获取url
            url = request.url
            if url:
                try:
                    
                    spider.driver.get(url)
                    page = spider.driver.page_source
                    if page:
                        """
                        self, url, status=200, headers=None, body=b''(相应结果), flags=None, request=None):
            self.headers = Headers(headers or {}
                        """
                        return HtmlResponse(url=url,status=200,body=page.encode('utf-8'),request=request)
                except TimeoutException as err:
                    print("请求超时",url)
                    return HtmlResponse(url=url, status=408, body=b"", request=request)

切记

激活中间件

SPIDER_MIDDLEWARES = {
    'downloadware.middlewares.DownloadwareSpiderMiddleware': 543,
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343