爬虫15:反“反爬“

主题一:反爬

为什么爬虫不用太担心禁止IP的反爬方法(顶多封几个小时IP,我们也有对应的方案),原因如下:

1.比方说一个学校/网吧,对外的IP只有一个或者几个,大家都通过局域网对外访问,如果因为学校里面的学生写了一个爬虫而把公网IP封了,那么整个学校1万多人都无法访问该的网站,损失量巨大

2.IP是动态分配的,如果将IP禁止访问,当把此IP分配给其他"无辜的人"时,他就无法登陆你的网站

信心来源:理论上,网站是不可能从技术上禁止爬虫,最后肯定是爬虫获胜的 alt

主题二:scrapy架构图

alt

看图中的序号和主件。

  • SPIDER:爬虫;1.用于yield出各个请求;2.定义回调函数处理请求下载的response,提取数据;3.对应提取出来的数据yield出item

  • ENGINE:引擎,理解成调度机制(大总管),当序号1来的请求进入到ENGINE,就会被识别为请求并经过序号2被放到调度器中。

  • SCHEDULER:调度器,涉及爬取顺序:队列(广度优先)、堆栈(深度优先),将准备好的请求一个个丢给ENGINE(序号3)

  • DOWNLOADER:下载器,下载网页内容。途径4和5是下载中间件,对请求和响应做拦截,比如对请求统一的加头、加代理或者集成selenium自己爬取。经过序号5回到ENGINE中,被ENGINE识别为响应,就回到SPIDERS中做解析。

  • ITEM PIPELINES:管道,负责数据的清洗和入库

主题三:针对反"反爬"的几个注意点

1.如何随机的更换user-agent(用户-代理)

首先,scrapy提供了一个默认的UserAgentMiddleware(源码),用于从配置文件中读取USER_AGENT,给request的请求头设置上

但是如果老是用同一个USER_AGENT去爬,则很容易被反爬,所以有了更换请求头的中的USER_AGENT。

要使用我们自己的中间件来实现随机变换user-agent,就需要将scrapy默认的这个中间件置为None,并配置上自己写的middlewares.

alt

具体如下:在scrapy项目新建之初,项目目录下就有一个middlewares.py的文件用于编写自定义的中间件。

alt

process_request()请求来的时候做拦截,它有三种类型的返回值

  • return none:程序继续往后执行
  • return response对象:不走downloader,直接把响应对象返回给engine,然后给spider做解析
  • return request对象:不走downloader,直接把请求对象返回给engine,然后丢到调度器中排序继续来发请求
  • raise异常:流程转到 process_exception方法中处理(常用于拦截不需要下载的url)
#request.headers是一个Headers对象,继承至字典
print(type(request.headers))
print(request.headers)
from scrapy.http.headers import Headers
# 给USER_AGENT随机换值,也可以这么写
request.headers['User-Agent']='UserAgent().random'
2.IP的变化方案:设置IP代理
alt

在刚刚上面的中间件的process_request()末尾加上代理,语句就一句,如下:

request.meta["proxy"] = 'http://IP地址:端口'

但是不能只用1个IP,而需要一个IP代理池

百度中搜索IP代理,将高匿的IP代理爬取下来放在数据库中,每次随机取一条即可,专门写一个工具来处理,如下:

import requests
from scrapy.selector import Selector
import MySQLdb

conn = MySQLdb.connect(host="127.0.0.1", user="root", passwd="root", db="article_spider", charset="utf8")
cursor = conn.cursor()


def crawl_ips():
    #爬取西刺的免费ip代理
    headers = {"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0"}
    for i in range(1568):
        re = requests.get("http://www.xicidaili.com/nn/{0}".format(i), headers=headers)

        selector = Selector(text=re.text)
        all_trs = selector.css("#ip_list tr")
        ip_list = []
        for tr in all_trs[1:]:
            speed_str = tr.css(".bar::attr(title)").extract()[0]
            if speed_str:
                speed = float(speed_str.split("秒")[0])
            all_texts = tr.css("td::text").extract()

            ip = all_texts[0]
            port = all_texts[1]
            proxy_type = all_texts[5]

            ip_list.append((ip, port, proxy_type, speed))

        for ip_info in ip_list:
            cursor.execute(
                "insert proxy_ip(ip, port, speed, proxy_type) VALUES('{0}', '{1}', {2}, 'HTTP')".format(
                    ip_info[0], ip_info[1], ip_info[3]
                )
            )

            conn.commit()


class GetIP(object):
    def delete_ip(self, ip):
        #从数据库中删除无效的ip
        delete_sql = """
            delete from proxy_ip where ip='{0}'
        """
.format(ip)
        cursor.execute(delete_sql)
        conn.commit()
        return True

    def judge_ip(self, ip, port):
        #判断ip是否可用
        http_url = "http://www.baidu.com"
        proxy_url = "http://{0}:{1}".format(ip, port)
        try:
            proxy_dict = {
                "http":proxy_url,
            }
            response = requests.get(http_url, proxies=proxy_dict)
        except Exception as e:
            print ("invalid ip and port")
            self.delete_ip(ip)
            return False
        else:
            code = response.status_code
            if code >= 200 and code < 300:
                print ("effective ip")
                return True
            else:
                print  ("invalid ip and port")
                self.delete_ip(ip)
                return False
    def get_random_ip(self):
        #从数据库中随机获取一个可用的ip
        random_sql = """
              SELECT ip, port FROM proxy_ip
            ORDER BY RAND()
            LIMIT 1
            """

        result = cursor.execute(random_sql)
        for ip_info in cursor.fetchall():
            ip = ip_info[0]
            port = ip_info[1]

            judge_re = self.judge_ip(ip, port)
            if judge_re:
                return "http://{0}:{1}".format(ip, port)
            else:
                return self.get_random_ip()

# print (crawl_ips())
if __name__ == "__main__":
    get_ip = GetIP()
    get_ip.get_random_ip()

改为加上这句request.meta["proxy"] = GetIP().get_random_ip()

github上scrapy-proxies,scrapy-crawlera有时间可以去了解下

tor 洋葱浏览器,经过洋葱网络后,自己的IP会被隐藏,其稳定性会比收费的IP代理还稳定一些,也可以去了解下

3.scrapy中集成selenium

middlewares.py中新建一个中间件类JSPageMiddleware,将其配置到settings中的DOWNLOAD_MIDDLEWARES

from selenium import webdriver
from scrapy.http import HtmlResponse
class JSPageMiddleware(object):
    #通过chrome请求动态网页
    def process_request(self, request, spider):
        if spider.name == "jobbole":
            browser = webdriver.Chrome(executable_path="D:/Temp/chromedriver.exe")
            browser.get(request.url)
            import time
            time.sleep(3)
            print ("访问:{0}".format(request.url))
            return HtmlResponse(url=browser.current_url, body=browser.page_source, encoding="utf-8", request=request)
  • process_requestreturn response对象:不走downloader不去下载,直接把响应对象返回给engine,然后给spider做解析

但是这样每次运行到中间件都会启动一个chrome,不合理,如下演变:

在JSPageMiddleware中间件类中添加一个构造函数__init__(),将browser作为对象属性,在初始化的时候给其赋值,变成这样:

class JSPageMiddleware(object):
    def __init__(self):
        self.browser=webdriver.Chrome(executable_path="")
        super().__init__()
    def process_request(self, request, spider):
        if spider.name == "jobbole":
            self.browser.get(request.url)
            import time
            time.sleep(3)
            print ("访问:{0}".format(request.url))
            return HtmlResponse(url=self.browser.current_url, body=self.browser.page_source, encoding="utf-8", request=request)

这种方式也存在问题: 当爬虫结束的时候,chrome浏览器没有自动关掉

方案: 可以将browser不放在中间件的对象属性中,而放在spider的属性中,中间件中的browser从spider中取:

alt

主题四:一些配置可以让爬虫被识别率降低

COOKIES_ENABLED = False禁用cookie;

有的网站会根据cookie进行分析,判断是否是爬虫,对于不需要登陆也能访问的网站,就把cookie禁用掉,Request就不会把cookie给带过去;

DOWNLOAD_DELAY = 10 对同一个网站的下一个url的延迟时间

AUTOTHROTTLE_ENABLED = TRUE 启用AutoThrottle扩展,然后可以设置一些延迟,比如AUTOTHROTTLE_START_DELAY(初始下载延迟),AUTOTHROTTLE_MAX_DELAY(最大的下载延迟)等(查文档)

如何在不同的spider里面配置一些不同的setting值

有些网站我们需要登陆才能访问,就需要开启cookie,有些网站需要禁用cookie,而settings.py中的设置就一个,此时就需要针对不同的爬虫设置不同的setting值.

Spider类中有一个custom_settings字典属性,子类中重写这个属性,把配置写在这里面,就可以使用各自的配置

相应的视频教程在这里 https://mp.weixin.qq.com/s/DG-T965Y9yKLwNYPus2cXA

本文由 mdnice 多平台发布

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值