在网络爬虫的开发过程中,重定向是一个常见的现象,尤其是在访问大型网站如百度时。重定向可以是临时的,也可以是永久的,它要求爬虫能够自动跟踪并正确处理这些跳转。本文将探讨如何使用 Python 编写爬虫以自动跟踪并处理百度页面的重定向。
理解 HTTP 重定向
HTTP 重定向是服务器告诉客户端(如浏览器或爬虫)请求的资源现在位于另一个 URL。HTTP 状态码 301(永久移动)和 302(临时移动)是最常见的重定向状态码。
301 重定向
表示资源已被永久移动到新的 URL,爬虫应该更新其索引以使用新的 URL。
302 重定向
表示资源临时移动到新的 URL,爬虫可以继续使用原始 URL。
使用 Python urllib 处理重定向
Python 的 urllib
模块提供了处理 HTTP 请求的工具,包括自动处理重定向。然而,有时候我们需要更细粒度的控制,例如限制重定向次数或记录重定向历史。
自动处理重定向
urllib
的 urlopen
函数会自动处理重定向,但默认情况下不提供重定向的详细信息。以下是一个示例,展示如何使用 urllib
自动处理重定向:
python
import urllib.requestdef fetch_url(url):try:response = urllib.request.urlopen(url)return response.read().decode('utf-8')except urllib.error.URLError as e:print(f"Failed to reach a server: {e.reason}")return None# 使用示例
content = fetch_url('http://www.baidu.com')
自定义重定向处理
为了更细粒度的控制,我们可以自定义重定向处理逻辑:
python
from urllib import request, errorclass RedirectHandler(request.HTTPRedirectHandler):def __init__(self, max_redirects=10):super().__init__()self.max_redirects = max_redirectsself.redirect_count = 0def http_error_302(self, req, fp, code, msg, headers):self.redirect_count += 1if self.redirect_count >= self.max_redirects:raise error.HTTPError(req.full_url, code, msg, headers, fp)return super().http_error_302(req, fp, code, msg, headers)def fetch_url_with_redirect_handling(url):opener = request.build_opener(RedirectHandler())request.install_opener(opener)try:with request.urlopen(url) as response:return response.read().decode('utf-8')except error.HTTPError as e:print(f"HTTP error: {e.code}")return Noneexcept error.URLError as e:print(f"URL error: {e.reason}")return None# 使用示例
content = fetch_url_with_redirect_handling('http://www.baidu.com')
持久连接
持久连接允许在一个 TCP 连接上发送多个 HTTP 请求和响应,减少了连接建立和关闭的开销。urllib
模块在 Python 3.6 之后默认支持 HTTP/1.1 的持久连接。
使用 http.client 实现持久连接
以下是一个使用 http.client
实现持久连接的示例:
import http.client
from urllib.parse import urlparse
from http.client import HTTPResponse# 代理服务器设置
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"class PersistentHTTPConnection(http.client.HTTPConnection):def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, proxy_info=None):super().__init__(host, port, timeout)self.proxy_info = proxy_infodef connect(self):# 连接到代理服务器super().connect()if self.proxy_info:# 使用 Basic Auth 认证username, password = self.proxy_infocredentials = f"{username}:{password}"credentials = "Basic " + credentials.encode('utf-8').base64().decode('utf-8')self.sock.sendall(b"Proxy-Authorization: " + credentials.encode('utf-8'))class PersistentHTTPConnectionWithProxy(PersistentHTTPConnection):def __enter__(self):return selfdef __exit__(self, exc_type, exc_val, exc_tb):self.close()def fetch_with_persistent_connection(url, proxy_info=None):parsed_url = urlparse(url)conn = PersistentHTTPConnectionWithProxy(parsed_url.netloc, proxy_info=proxy_info)conn.connect() # 连接到代理服务器conn.request("GET", parsed_url.path)response = conn.getresponse()if response.status == 200:return response.read().decode('utf-8')else:print(f"HTTP error: {response.status}")return None# 使用示例
content = fetch_with_persistent_connection('http://www.baidu.com', (proxyUser, proxyPass))