title: scrapy反爬虫策略date: 2018-06-06 00:51:34
tags: spider

scrapy框架图

scrapy反爬虫策略 - 图1

scrapy反爬虫策略 - 图2

组件

①引擎(Scrapy)

②调度器(Scheduler)

③下载器(Downloader)

④爬虫(Spiders)

⑤项目管道(Pipeline)

⑥下载器中间件(Downloader Middlewares)

⑦爬虫中间件(Spider Middlewares)

⑧调度中间件(Scheduler Middewares)

运行流程

  1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
  2. 引擎把URL封装成一个请求(Request)传给下载器
  3. 下载器把资源下载下来,并封装成应答包(Response)
  4. 爬虫解析Response
  5. 解析出实体(Item),则交给实体管道进行进一步的处理
  6. 解析出的是链接(URL),则把URL交给调度器等待抓取

Request类 和 Response类

resquest由spiders产生

response由downloader产生

具体函数用法及其参数可以通过查看源码和文档了解。

User Agent用户代理

User Agent

在setting中设置可能现有的User Agent:

  1. user_agent_list = []

在需要的文件中import。

通过生成随机数,来取user_agent:

  1. import random
  2. random_index = random.randint(0, len(user_agent_list) - 1)
  3. random_agent = user_agent_list[random_index]

可以在需要使用的地方写该段代码,但缺少了代码复用。

可以通过github上一个开源的库fake-useragent来动态获取user-agent。

实例:

  1. from fake_useragent import UserAgent
  2. ua = UserAgent()
  3. # 指定浏览器
  4. us.ie
  5. us.chrome
  6. us.firefox
  7. # 随机浏览器
  8. us.random

Downloader Middlewares(下载器中间件)

由于上述的UserAgent的获取没进行代码复用,使用中间件可以进行设置。

可以在setting中配置Downloader Middlewares,默认的放在DownloaderMiddlewares类的useragent下。

自定义DownloaderMiddleware:

  1. class RandomUserAgentMiddlware(object):
  2. # 随即更换UserAgent
  3. def __init__(self, crawler):
  4. super(RandomUserAgentMiddlware, self).__init__()
  5. self.ua = UserAgent()
  6. self.ua_type = crawler.settings.get("RANDOM_US_THPE", "random")
  7. def from_crawler(cls, crawler):
  8. return cls(crawler)
  9. def process_request(self, request, spider):
  10. def get_ua():
  11. return getattr(self.ua, self.ua_type)
  12. request.headers.setdefault('User_Agent', get_ua())

并且可以在setting中指定获得的ua的类型,并通过写闭包函数,用getattr方法将类型连接。

  1. RANDOM_UA_TYPE = "random"

IP代理池

  1. 对于亚马逊的服务器,可以重启动态分配IP(阿里云除外,为静态IP)。公司、家中的路由器提供的IP也是动态分配的。本机IP是爬取效果最好的(IP代理较慢),最好是控制好爬取速度,保证本地IP不被封禁。

scrapy反爬虫策略 - 图3

方法一:通过爬取西刺免费IP代理来获取IP代理。

在项目根目录下新建文件夹tools,存放脚本文件crawl_xici_ip.py

连接数据库
conn = MySQLdb.connect(host="xxx.xxx.xxx.xxx", user="username", passwd="password", db="dbname", charset="utf8")

爬取西刺免费ip代理
def crawl_ips():
    # 爬取西刺的免费ip代理
    headers = {
        "User-Agent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/537.36"}
    for i in range(3205):
        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_first()
            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, 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[2])
            )
            conn.commit()

获取IP
class GetIP(object):
    def delete_ip(self, ip):
        # 从数据库中删除无效的ip
        delete_sql = """
            delete from xwd_proxy_ip where ip='{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 200 <= code < 300:
                print("effective ip")
                return True
            else:
                print("invalid ip and port")
                self.delete(ip)
                return False

    def get_random_ip(self):
        # 从数据库中随机获取一个可用的IP
        random_sql = """
            SELECT ip, port FROM xwd_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()

注意一定要加上“if name == “main“:”这条代码,否则在别的spider文件中调用时会执行下面两条代码。

可以从GitHub上的项目scrapy-proxies中得到更完善的获取IP代理的脚本。

在DownloadMiddleware中添加获取IP的类:

class RandomProxyMiddleware(object):
    def process_request(self, request, spider):
        get_ip = GetIP()
        request.meta["proxy"] = get_ip.get_random_ip()

方法二:通过收费项目scrapy-crawlera来获取IP代理服务

具体使用方法文档中有写,使用流程非常简单。

方法三:通过洋葱网络Tor来包装IP

使用洋葱网络可以将自己的IP地址进行包装,达到匿名爬取网站的效果,而且效率要比网上免费IP代理甚至收费IP代理更好。主要问题是需要VPN。

验证码识别

前言

无需自己编写验证码识别代码,由于很多识别工具是需要通过训练集训练的,并且自己实现非常复杂,效果也不好。

常见方法

编码实现(tessract-ocr)—— 识别率低

在线打码 —— 识别率90%

人工打码 —— 识别率基本100%

在线打码

可以使用在线付费打码平台,如云打码可以很好的实现常见验证码识别。具体使用文档可自行查看官网。

隐藏爬虫行为的配置

在setting中设置:

具体内容和详情可以参照scrapy官方文档(中文文档),在其中可以查询到setting详细信息。

Cookie禁用

COOKIES_ENABLED = False

自动限速

CONCURRENT_REQUESTS = 32
DOWNLOAD_DELAY = 3

文档

Spider设置特定setting

在爬虫class信息中设置特定的setting: