更多优质内容
请关注公众号

爬虫进阶之Scrapy(六) scrapy中的反反爬虫技术-阿沛IT博客

正文内容

爬虫进阶之Scrapy(六) scrapy中的反反爬虫技术

栏目:Python 系列:scrapy爬虫系列 发布时间:2019-12-26 15:56 浏览量:9379

所谓反爬虫就是被爬取的网站通过一定的技术手段如判断User-Agent或者限制IP等方式防止爬虫爬取,而反反爬虫则是有针对性的通过技术手段突破这些限制,依旧爬取到想获取的信息。本文主要介绍几个实用的反反爬虫的技术。


1.切换User-Agent:
最简单的方法:在settings.py中设置多个User-Agent ,把他定义成一个常量

USER_AGENT_LIST = [ "...", "...",...]


然后再spider中

import settings 
import random
....

def getUA(self):
	index=random.randint(0,len(settings.USER_AGENT_LIST)-1)
	ua=settings.USER_AGENT_LIST[index]
	self.headers["User-Agent"]=ua


这个方法是放在spider中,当你在spider中凡是有yield scrapy.Request(...)的时候,就调用一下这个getUA方法,这样User-Agent就会改变。

但是每次Request都要获取调用getUA函数会很难受。

所以我们写在中间件中。


scrapy的源码中其实就有一个useragent.py,里面写着一个
UserAgentMiddleware中间件,它里面做了一件事情,他从settings获取USER_AGENT这个常量,并且将他设成默认header

中间件的源码位于D:\python3.6\Lib\site-packages\scrapy\downloadermiddlewares

在中间件中,可以修改request和response,而且是全局性的修改。

如果我们要写自己的user-agent中间件,我们必须先将他源码中的user-agent中间件取消掉。


DOWNLOADER_MIDDLEWARES = {
	#scrapy目录下的downloadermiddlewares目录下的useragent.py文件的UserAgentMiddleware类设为none
	'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':None
}


现在我们在middlewares.py中写自己的useragent中间件类

class RandUAMiddleware(object):
	def __init__(self,crawler):
		super(RandUAMiddleware,self).__init__()
		self.user_agent_list = crawler.settings.get("user_agent_list")
		
		
	@classmethod
	def from_crawler(cls,crawler):
		return cls(crawler)
		
	def process_request(self,request,spider):
		request.headers.setdefault("User-Agent",random.choice(self.user_agent_list))


这里使用from_crawler是为了获取配置信息。

定义了from_setting或者from_crawler就必须return cls(...),而且必须定义__init__

上面的return cls(crawler) 中的cls就是RandUAMiddleware类本身。cls()是实例化RandUAMiddleware类并且向这个类的初始化方法传入一个crawler参数。
所以__init__方法中有一个crawler的形参供传递。


可是,自己定义的User-Agent毕竟有限,如果想要获取无限随机的User-Agent,可以实用python的fake-useragent模块


pip install fake-useragent


使用方法:

obj = fake_useragent.UserAgent()
ua = obj.random


集成到scrapy的middleware中

from fake_useragent import UserAgent 

class RandUAMiddleware(object):
	def __init__(self):
		super(RandUAMiddleware,self).__init__()
		self.ua = UserAgent()
		
	@classmethod 
	def from_crawler(cls,crawler):
		return cls(crawler)
		
	def process_request(self,request,spider):
		request.headers.setdefault("User-Agent",self.ua.random)


PS:如果request.headers.setdefault()方法失效了,导致User-Agent没有填进去,我们可以这样:
request.headers['User-Agent'] = self.ua.random


2.设置代理IP

有些网站会通过检测一段时间内某个IP的访问次数来判断你是否在用爬虫爬取他的网站信息,如果几秒内这个ip的访问次数达到上百次,那很明显就是有爬虫在爬取,此时这个IP就会被列入黑名单,运气好只会限制该IP一段时间不能访问,运气不好可能会永久封禁。这种方式是最常见也最有效的一种的反爬虫的技术。

而想要突破这种防范,我们就要使用多个IP轮流访问,这样相当于是很多用户在正常访问这个网站。为此我们需要使用代理IP。


代理ip的原理:
假如有一个23.225.228.56:80的代理ip,我们要爬取lagou.com 
原本我们是直接请求lagou.com的服务器
但现在,我是先请求23.225.228.56的服务器,之后这个服务器再去请求拉钩网的服务器并将数据返回给我。


设置代理ip很简单,只需要在middleware的process_request中这样:

request.meta["proxy"]="http://221.238.67.231:8081
return None 


即可


但关键是怎么获取这些代理IP

接下来我们要设置一个ip代理池,做法是,在“快代理”这个免费代理ip网中爬取他的代理ip,并保存到数据库中。


“快代理”代理IP


等我们要用到这些代理ip的时候,直接从数据库里面调用就是。

获取3个字段:类型(http/https),ip,端口

创建数据表

create table proxy(
	id int unsigned not null primary key,
	type enum("http","https") not null default "http",
	ip varchar(20),
	port varchar(10)
);


爬取代理网站的蜘蛛如下:

import scrapy
from lagou.items import ProxyItem

class ProxySpider(scrapy.Spider):
    name = 'proxy'
    start_urls= ['https://www.kuaidaili.com/free/inha']
    page=150

    def parse(self,response):
        #获取前150页的ip
        for i in range(1,self.page+1):
            page_url=self.start_urls[0]+"/"+str(i)
            yield scrapy.Request(page_url,callback=self.parseIP)

    def parseIP(self,response):
        # trs=response.xpath("//table[@id=ip_list]//tr")[1:]
        trs=response.xpath("//tbody/tr")
        for tr in trs:
            item = ProxyItem()
            item['type']=tr.xpath("td[4]/text()").extract_first().lower().strip()
            item['ip']=tr.xpath("td[1]/text()").extract_first().strip()
            item['port']=tr.xpath("td[2]/text()").extract_first().strip()

            yield item
			


item如下:

class ProxyItem(scrapy.Item):
    type=scrapy.Field()
    ip = scrapy.Field()
    port = scrapy.Field()

    def get_insert_sql(self):
        sql="insert into proxy values (%s,%s,%s,%s)"

        params=(None,self['type'],self['ip'],self['port'])

        return sql,params


pipeline如下:

class DBPipline(object):
    def open_spider(self,spider):
        self.dbpool = adbapi.ConnectionPool("pymysql",**spider.settings.get("DB_CONF"))

    def close_spider(self,spider):
        pass

    def process_item(self,item,spider):
        query = self.dbpool.runInteraction(self.do_insert,item)
        query.addErrback(self.handle_err)

    def handle_err(self,err):
        print(err)

    def do_insert(self,cursor,item):
        sql,params=item.get_insert_sql()
        cursor.execute(sql,params)


这样就能够获取很多免费的代理IP,但是免费的代理IP有很多是无效的,不能访问的,为此还写了一个用来获取有效代理ip的类,放在tool目录中,下面的脚本要爬取到代理IP之后才能执行:

class ProxyTool:
    db_conf={
        "host":"127.0.0.1",
        "user":"root",
        "password":"573234044",
        "database":"test",
        "charset":"utf8"
    }
    test_url="http://www.baidu.com"
    header={
        "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36"
    }

    def __init__(self):
        #连接数据库
        self.conn=pymysql.connect(**self.db_conf)
        self.cursor=self.conn.cursor(cursor=pymysql.cursors.DictCursor)

    # 主方法:返回一个可用的Proxy
    def get(self,type=None):
        #从数据库中随机获取一个Proxy
        proxyDict=self.getProxy(type)

        #测试获取的Proxy是否可用
        res=self.testProxy(proxyDict)

        #如果res为True,则返回proxy字符串
        if res:
            proxy = self.concatProxy(proxyDict)
            return proxy
        else:
            #proxy不可用则递归
            return self.get(type)  #这里的return不能没有


    # 从数据库中随机获取一个Proxy
    def getProxy(self,type):
        if type:
            sql = "select * from proxy where type = %s order by rand() limit 1" % type
        else:
            sql = "select * from proxy order by rand() limit 1"

        num = self.cursor.execute(sql)
        if num == 0:
            raise Exception("proxy表中代理ip为空")

        res = self.cursor.fetchone()

        return res

    #测试获取的Proxy是否可用
    def testProxy(self,proxyDict):
        #使用该Proxy访问百度
        proxy={proxyDict['type'].lower():proxyDict['ip']+":"+proxyDict['port']}
        try:
            res=requests.get(self.test_url,proxies=proxy,headers=self.header)
        except BaseException as e:
            print(e)
            self.delProxy(proxyDict['ip'])
            return False
        else:
            #如果没有报错,判断状态码
            if res.status_code <200 or res.status_code>=300:
                self.delProxy(proxyDict['ip'])
                return False

        return True

    #删除代理
    def delProxy(self,proxyIp):
        sql = "delete from proxy where ip = %s"
        res = self.cursor.execute(sql,(proxyIp,))

        if res>0:
            print("删除无效代理ip:%s" % proxyIp)

    #拼接proxyDict
    def concatProxy(self,proxyDict):
        proxy = proxyDict['type']+"://"+proxyDict['ip']+":"+proxyDict['port']
        return proxy

if __name__ == "__main__":
    tool = ProxyTool()
    for i in range(1000):
        proxy=tool.get()
        print(proxy)
        print("#########################################\n")


每调用一次tool.get()方法就会从数据库中获取代理(所以要先爬到很多代理ip才能用ProxyTool类去获取代理ip),然后用这个代理访问百度,如果访问百度失败说明是个无效ip
就会从数据库中干掉。

结果发现,每10个ip只有1个能用,而且检测的过程很耗时间。

结论:使用免费代理ip不是一个可靠的方案


还有其他两种可靠方案:
crawlera:官方ip代理插件,需要去官网购买key,可靠稳定,强大
tor洋葱网络 : 匿名发送数据,需要翻墙,稳定性非常高


3. 限速爬取

如果使用免费代理IP觉得很慢,又不想花钱去买高效的代理IP,我们可以通过限速爬取来应对封禁IP的防范,当然是以降低爬取速度作为牺牲

scrapy有一套自己的限速机制,但是要在settings.py中设置才行。
找到
DOWNLOAD_DELAY = 1
AUTOTHROTTLE_ENABLED = True
这两句
然后将他们的注释去掉

DOWNLOAD_DELAY设置的越大,爬取的速度越慢,越能有效防止被封


4.禁用cookie
有些网站的反爬虫是通过检测的请求中的cookie信息进行的
对于一些不需要登陆的网站,我们禁用cookie即可反反爬虫

setting中的:
COOKIES_ENABLED = False
将这句注释去掉即可


5.使用selenium模块进行动态爬取

有一些网站的内容是通过js动态加载的,例如使用ajax请求,这样动态加载的内容无法出现在源代码中,我们就无法获取到这部分内容。有时候,这是因为业务逻辑的需求要使用 js 动态加载,但是有时候也是因为防止爬虫而使用 js 加载。

selenium的原理是模仿浏览器访问页面,将动态加载的内容也渲染到页面时候,再获取渲染好的页面代码。从而解决这个问题。

selenium的使用具体在下一节介绍





更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > 爬虫进阶之Scrapy(六) scrapy中的反反爬虫技术

热门推荐
推荐新闻