使用同一个IP地址频繁抓取数据,IP容易被封。可以使用代理proxy解决这个问题。

1. 代理proxy

使用同一个IP地址频繁抓取数据,IP容易被封。使用HTTP代理可以解决这个问题。HTTP代理有两种:

(1)普通代理

该代理扮演着中间人角色,替客户向端向服务发送请求,并替服务器将响应发送给客户端,如下图所示:

HTTP普通代理示意图 图1 HTTP普通代理示意图(图来源于《HTTP权威指南》)

代理服务器对接收到的请求进行解析,重新封装后发给服务器;服务器响应后,代理服务器对响应进行解析,重新封装再发送给客户端。

(2)隧道代理

隧道协议(Tunneling Protocol)使用一种发送协议,将另一个不同的网络协议封装在发送协议的正文部分。使用隧道可以在不兼容的网络上传输数据,或为不安全网络提供一个安全访问。

隧道代理示意图 图2 隧道代理示意图(图来源于《HTTP权威指南》)

本文先介绍如何使用普通代理实现抓取。

2. 使用普通代理

(1)使用requests

调用requests.get(url, params=None, **kwargs),传入关键字proxiesproxies是字典型,将协议或者协议+主机映射到代理的URL,如{‘http’: ‘foo.bar:3128’, ‘http://host.name’: ‘foo.bar:4012’}。简单例子如下:

import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
ua=UserAgent()

proxies = {'http' : '123.207.11.119:1080', 'http' : '140.143.142.218:1080'}
response = requests.get('http://baidu.com', proxies=proxies, headers={'User-Agent': ua.random})

soup = BeautifulSoup(response.text, 'lxml')

(2)使用urllib

直接上代码,如下:

import urllib
from bs4 import BeautifulSoup

from fake_useragent import UserAgent
ua=UserAgent()

proxies = {'http' : '123.207.11.119:1080', 'http' : '140.143.142.218:1080'}

proxy_handler = urllib.request.ProxyHandler(proxies)# Cause requests to go through a proxy
opener = urllib.request.build_opener(proxy_handler) # construct a new opener using your proxy settings
urllib.request.install_opener(opener)               # install the opener on the module-level

req = urllib.request.Request('http://baidu.com', headers={'User-Agent': ua.random})
f = urllib.request.urlopen(req)
soup = BeautifulSoup(f, 'lxml')

尽管使用了代理,但使用一个代理频繁抓取,也会面临代理IP被封的问题。解决该问题,收集一些代理IP,形成代理IP池,轮流使用。

3. 代理IP池

有不少网站提供免费代理IP,比如我之前用的国内高匿免费HTTP代理xicidaili.com,可惜现在用不了。

国内高匿免费HTTP代理 图3 国内高匿免费HTTP代理

写一段简单代码,将代理IP保存成字典列表http_type : "{http_type}://{ip}:{port}"格式,如下:

def get_list_proxy_dicts():
    urls = ['https://www.xicidaili.com/nn/{}'.format(i) for i in range(2, 5)]

    for url in urls:
        web_data = requests.get(url, headers=headers)
        soup = BeautifulSoup(web_data.text, 'lxml')

        rows = soup.find_all('tr')

        list_proxy_dicts = []
        for i, row in enumerate(rows):
            tds = row.find_all('td')

            http_type = tds[5].text.lower()
            if http_type not in ['http', 'https']:
                continue

            proxy_dict = {http_type : "{http_type}://{ip}:{port}".format(
                                                http_type=http_type, 
                                                ip=tds[1].text, 
                                                port=tds[2].text)}

            if is_available_proxy(proxy_dict):
                list_proxy_dicts.append(proxy_dict)

    return list_proxy_dicts

抓取到的IP池,不见得每个都有效,需要事先验证,确认有效再加入IP池。这里用的验证方法很简单,看该代理能否正常访问百度。

def is_available_proxy(proxy_dict):    
    try:
        proxy_handler = urllib.request.ProxyHandler(proxy_dict)
        opener = urllib.request.build_opener(proxy_handler)
        opener.addheaders = [('User-agent', 'Mozilla/5.0')]
        urllib.request.install_opener(opener)
        req = urllib.request.Request('http://www.baidu.com') 
        sock = urllib.request.urlopen(req)
    except urllib.error.HTTPError as e:
        print('Error code: ', e.code)
        return False
    except Exception as detail:
        print("ERROR:", detail)
        return False

    return True

参考资料:

[1] 普通代理:RFC 7230 - HTTP/1.1: Message Syntax and Routing

[2] 隧道代理:Tunneling TCP based protocols through Web proxy servers

本文系Spark & Shine原创,转载需注明出处本文最近一次修改时间 2022-03-17 22:51

results matching ""

    No results matching ""