2017/4/23 如何使爬虫更像人

作者: Carpe | 来源:发表于2017-04-25 14:49 被阅读499次

    作业思路

    昨天在尝试了几次拉勾网的爬取,因为调试了几次后运行,然后IP就被封了
    谷歌了一番,发现谷歌上还是比较少关于这方面的教程,要么是重复,要么是一笔带过,心塞……
    进入正题:一个爬虫如何反Ban?

    让请求更像是浏览器发出的

    之前只是用了一个默认的头,这样很容易被网站给识别出来,毕竟你一个浏览器短时间发出这么多请求,非人也,所以可以联想到用很多个浏览器,这样就降低了一个浏览器发出请求的数量,看起来合理了那么些,列出很多浏览器的头,然后随机选,那头哪里来呢?这里推荐一个网站
    想要更多头点我
    代码在这里:
    middlewares.py

    # -*-coding:utf-8-*-
    
    import random
    from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
    
    
    class RotateUserAgentMiddleware(UserAgentMiddleware):
      #初始化头
        def __init__(self, user_agent=''):
            self.user_agent = user_agent
    
      #通过random函数,随机选择头,然后伪装请求
        def process_request(self, request, spider):
            ua = random.choice(self.user_agent_list)
            if ua:
                print ua, '-----------------'
                request.headers.setdefault('User-Agent', ua)
    
        # 罗列出头
        user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
        ]
    

    setting.py

    DOWNLOADER_MIDDLEWARES = {
        'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':None,
        'lagou.middlewares.RotateUserAgentMiddleware':400,
    }
    

    让手速慢一点

    一个爬虫一秒钟请求N次,这不就是告诉别人说:“你看,我是爬虫,我的手速有多快呀”,然后ban的就是你这只爬虫,所以可以设置一下下载延迟(setting.py):
    DOWNLOAD_DELAY = 1,虽然是慢一点,但是稳定,保险。

    多个地点一起请求

    如何来改变自己请求的地点?一般来说,我们的IP地址就是我们的地理位置标签,所以只要改变我们的IP,就可以改变我们请求的位置,随机IP请求,就能够模拟出来自全国各地的用户来访问。
    所以在这里就需要用到代理IP池了,代理IP可以在通过网站上提供的,更多代理IP请点我
    具体如何实现呢?

    middlewares.py编写一个设置代理的类

    import random
    from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
    import base64
    from settings import PROXIES
    
    class ProxyMiddleware(object):
      #编写传递请求的方法
        def process_request(self, request, spider):
            proxy = random.choice(PROXIES)
      #随机选择设置中的代理
            if proxy['user_pass'] is not None:
                request.meta['proxy'] = "http://%s" % proxy['ip_port']
                #request.meta是一个字典,包含了很多请求附加信息,这里是更改请求的IP
                encoded_user_pass = base64.encodestring(proxy['user_pass'])
                #Base64是一种基于64个可打印字符来表示二进制数据的表示方法
                request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
                print "Successful" + proxy['ip_port']
            else:
                print "Fail" + proxy['ip_port']
                request.meta['proxy'] = "http://%s" % proxy['ip_port']
        #再换一个IP...注意是一次请求用一个有效的IP
    

    分析:
    在注释中提到request.meta,先来探究一下request.meta中到时有哪些东西,用scrapy shell测试了一下,所返回的内容是:

    {'depth': 0,
     'download_latency': 16.514000177383423,
     'download_slot': 'docs.python-requests.org',
     'download_timeout': 180.0,
     'handle_httpstatus_list': <scrapy.utils.datatypes.SequenceExclude at 0x45a84b0>,
     'proxy': 'http://125.73.40.123:8118'}
    

    从这里看,我们可以先更改请求的proxy
    那么我们再来探究一下request.headers里有什么内容,同样调试,得到

    {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
     'Accept-Encoding': 'gzip,deflate',
     'Accept-Language': 'en',
     'Proxy-Authorization': 'Basic ',
     'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3'}
    

    从字典上看需要修改的是proxy-authorization(因为其他的都是固定的)

    setting.py添加IP

    PROXIES = [
        {'ip_port': '119.5.1.38:808', 'user_pass': ''},
        {'ip_port': '115.216.31.87:8118', 'user_pass': ''},
        {'ip_port': '125.73.40.123:8118', 'user_pass': ''},
        {'ip_port': '171.38.35.20:8123', 'user_pass': ''},
        {'ip_port': '171.38.35.97:8123', 'user_pass': ''},
        {'ip_port': '222.85.39.136:808', 'user_pass': ''},
    ]
    

    setting.py设置

    DOWNLOADER_MIDDLEWARES = {
       'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': 110,       
       'lagou.middlewares.ProxyMiddleware': 100,
    }
    

    如何来获取代理IP

    上面有提供获取代理IP的网址,这里来说一下如何批量采集
    编写脚本:

    get_ip.py
    # -*- coding: utf-8 -*-
    import lxml
    import requests
    import re 
    import sys
    from bs4 import BeautifulSoup
     
     
    f = open('proxy.txt' , 'w')
    headers = {"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"}
    
    for page in range(1, 101):
        link = 'http://www.xici.net.co/nn/' + str(page)
        
        html = requests.get(link, headers=headers)
        soup = BeautifulSoup(html.content, 'html.parser')
        trs = soup.find('table', id='ip_list').findAll('tr')
        for tr in trs[1:]:
            tds = tr.find_all('td')
            ip = tds[1].get_text()
            port = tds[2].get_text()
            protocol = tds[5].text.strip()
            if protocol == 'HTTP' or protocol == 'HTTPS':
                f.write('%s=%s:%s\n' % (protocol, ip, port) )
                print '%s=%s:%s' % (protocol, ip, port)
     
    f.close()
    

    但是,问题也来了,如何保证这些IP都是有用的呢?所以需要验证一下IP是否有用

    验证代理IP是否有效

    test_ip.py
    # -*- coding: utf-8 -*-
    import requests
    import sys
    import threading
    import time
     
    inFile = open('proxy.txt', 'r')
    outFile = open('available.txt', 'w')
    
    #of = open('proxy.txt' , 'w')
    headers = {"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"}
    
    lock = threading.Lock()
    
    def test():
        while True:
            line = inFile.readline()
            if len(line) == 0: break
            protocol, proxy = line.split('=')
            try:
                proxies = {protocol: proxy}
                r = requests.get("https://www.lagou.com", headers=headers, proxies=proxies)
          #采用requests的状态码来判断
                if r.status_code == requests.codes.ok:
                    print 'add proxy:' + proxy
                    outFile.write(proxy + '\n')
                else:
                    print  '...'
    
            except:
                print "fail"
    
    #采用多线程
    all_thread = []
    for i in range(50):
        t = threading.Thread(target=test)
        all_thread.append(t)
        t.start()
        
    for t in all_thread:
        t.join()
     
    inFile.close()
    outFile.close()
    

    作业结果:

    作业结果

    计算了一下页面上所有的个数,这个个数只和总数相差不超过5个,相较于第一次爬取的结果为50多个好了很多。

    作业中的问题

    啊...这是学爬虫来研究的最久的一篇...
    还有两个问题没有解决

    问题一:

    上面的代码

    encoded_user_pass = base64.encodestring(proxy['user_pass'])
    #Base64是一种基于64个可打印字符来表示二进制数据的表示方法
    request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
    

    为什么要加encoded_user_pass = base64.encodestring(proxy['user_pass'])

    问题二:

    在采用了代理IP后,测试过了,正式爬取的时候,还是有一些IP会有这样的信息显示

    计算机积极拒绝是什么鬼

    问题三:

    还是没有采用数据库的存储方式,下次改进

    相关文章

      网友评论

        本文标题:2017/4/23 如何使爬虫更像人

        本文链接:https://www.haomeiwen.com/subject/kgbmzttx.html