使用代理通过 Beautiful Soup 和 requests 抓取数据,指的是先把 requests 库配置为将网络流量路由到指定的代理服务器,再用 Beautiful Soup 解析 HTML 内容,从而实现 IP 轮换、绕过地域限制并减少 IP 封禁。
进行网页抓取时,从单一 IP 地址直接发出请求会导致速率限制、临时或永久 IP 封禁,或遇到地域受限内容。代理服务通过不同的 IP 地址转发请求来应对这些问题,使抓取更稳定、更易扩展。
完成该任务的核心库为:
* requests:Python 的 HTTP 库,简化 HTTP 请求的发送。
* Beautiful Soup:用于解析 HTML 和 XML 文档的 Python 库。
使用 requests 配置代理
requests 库通过请求方法中的 proxies 参数支持代理配置。
单个代理配置
要使用单个代理,请提供一个把协议(HTTP、HTTPS)映射到代理 URL 的字典。
import requests
proxy_http = "http://your_proxy_ip:port"
proxy_https = "https://your_proxy_ip:port" # 通常与 HTTP 相同
proxies = {
"http": proxy_http,
"https": proxy_https,
}
try:
response = requests.get("http://httpbin.org/ip", proxies=proxies, timeout=10)
response.raise_for_status()
print("External IP:", response.json().get('origin'))
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
代理认证
对于需要认证的代理,请把凭据直接嵌入代理 URL 中。
import requests
proxy_user = "your_username"
proxy_pass = "your_password"
proxy_host = "your_proxy_ip"
proxy_port = "port"
authenticated_proxy = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"
proxies = {
"http": authenticated_proxy,
"https": authenticated_proxy,
}
try:
response = requests.get("http://httpbin.org/ip", proxies=proxies, timeout=10)
response.raise_for_status()
print("External IP (authenticated):", response.json().get('origin'))
except requests.exceptions.RequestException as e:
print(f"Authenticated request failed: {e}")
轮换代理
在大规模抓取中,在一份代理列表上轮换是分散请求、降低被封风险的必备手段。
import requests
import random
import time
proxy_list = [
"http://user1:[email protected]:8000",
"http://user2:[email protected]:8000",
"http://user3:[email protected]:8000",
]
def get_random_proxy():
return random.choice(proxy_list)
def make_proxied_request(url, headers=None, attempt_limit=3):
for attempt in range(attempt_limit):
proxy_url = get_random_proxy()
proxies = {"http": proxy_url, "https": proxy_url}
try:
print(f"Attempt {attempt + 1}: Using proxy {proxy_url.split('@')[-1]} for {url}")
response = requests.get(url, proxies=proxies, headers=headers, timeout=15)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
print(f"Proxy request failed (attempt {attempt + 1}): {e}")
time.sleep(random.uniform(2, 5)) # 换用新代理重试前的延迟
return None
# 使用示例 URL 的用法演示
try:
target_url = "http://quotes.toscrape.com/"
response = make_proxied_request(target_url)
if response:
print(f"Successfully fetched {target_url} with status code {response.status_code}")
except Exception as e:
print(f"Failed to fetch {target_url} after multiple retries: {e}")
使用 Beautiful Soup 解析 HTML
Beautiful Soup 会把 requests 响应中的 HTML 内容转换为可遍历的 Python 对象。
from bs4 import BeautifulSoup
html_doc = """
<html>
<head><title>Example Page</title></head>
<body>
<p class="intro"><b>Welcome!</b></p>
<div id="content">
<a href="/item1" class="product-link">Product A</a>
<a href="/item2" class="product-link">Product B</a>
</div>
</body>
</html>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
# 访问元素
print("Page Title:", soup.title.string)
# 查找特定元素
intro_paragraph = soup.find('p', class_='intro')
print("Intro text:", intro_paragraph.get_text(strip=True))
# 按 class 查找所有元素
product_links = soup.find_all('a', class_='product-link')
for link in product_links:
print(f"Product: {link.get_text()}, URL: {link['href']}")
将代理与 Beautiful Soup 抓取整合
把代理配置与 Beautiful Soup 解析结合起来,即可构成完整的抓取流程。
import requests
from bs4 import BeautifulSoup
import random
import time
# --- 代理配置(同前文定义)---
proxy_list = [
"http://user1:[email protected]:8000",
"http://user2:[email protected]:8000",
# 从您的服务中添加更多代理
]
def get_random_proxy():
return random.choice(proxy_list)
def make_proxied_request(url, headers=None, attempt_limit=3):
for attempt in range(attempt_limit):
proxy_url = get_random_proxy()
proxies = {"http": proxy_url, "https": proxy_url}
try:
print(f"Attempt {attempt + 1} for {url}: Using proxy {proxy_url.split('@')[-1]}")
response = requests.get(url, proxies=proxies, headers=headers, timeout=15)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
print(f"Request failed (attempt {attempt + 1}): {e}")
time.sleep(random.uniform(2, 5))
return None
# --- 抓取逻辑 ---
def scrape_quotes(url):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = make_proxied_request(url, headers=headers)
if response:
soup = BeautifulSoup(response.text, 'html.parser')
quotes = soup.find_all('div', class_='quote')
data = []
for quote in quotes:
text = quote.find('span', class_='text').get_text(strip=True)
author = quote.find('small', class_='author').get_text(strip=True)
tags = [tag.get_text(strip=True) for tag in quote.find_all('a', class_='tag')]
data.append({"text": text, "author": author, "tags": tags})
return data
return []
# --- 执行 ---
if __name__ == "__main__":
target_url = "http://quotes.toscrape.com/"
print(f"Starting scrape of {target_url}")
scraped_data = scrape_quotes(target_url)
if scraped_data:
print(f"Scraped {len(scraped_data)} quotes:")
for i, quote in enumerate(scraped_data[:3]): # 为简洁起见只打印前 3 条
print(f" {i+1}. Author: {quote['author']}, Quote: {quote['text'][:50]}...")
else:
print("No data scraped.")
最佳实践与注意事项
User-Agent 请求头
Web 服务器常常检查 User-Agent 请求头来识别客户端。requests 的默认 User-Agent 会暴露爬虫身份。模仿常见浏览器的 User-Agent 可降低被检测的概率。
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Connection": "keep-alive",
}
response = requests.get(url, proxies=proxies, headers=headers)
请求延迟与限速
过快的请求会触发速率限制或 IP 封禁。请在请求之间加入延迟,尤其是在轮换代理时。使用带随机区间的 time.sleep() 效果良好。
import time
import random
# ... 在处理多个 URL 的循环内部 ...
time.sleep(random.uniform(2, 7)) # 等待 2 到 7 秒
response = make_proxied_request(next_url, headers=headers)
错误处理
稳定的抓取需要针对网络问题、代理故障和服务器响应做全面的错误处理。
* requests.exceptions.RequestException:捕获所有与 requests 相关的错误(连接、超时、HTTP 错误)。
* HTTP 状态码:检查 response.status_code。403(Forbidden)、404(Not Found)、429(Too Many Requests)或 5xx(Server Error)等状态码表示存在问题。
* 超时:在 requests.get() 中配置 timeout 参数,避免无限期等待。
CAPTCHA 与高级反抓取措施
部分网站采用 CAPTCHA 或 JavaScript 挑战等高级检测机制。代理有助于 IP 轮换,但无法直接解决这些问题。遇到这类情况,可考虑无头浏览器(例如 Selenium、Playwright)或专门的验证码识别服务。
代理类型对比
| 特性 | 数据中心代理 | 住宅代理 |
|---|---|---|
| IP 来源 | 商业服务器、云服务商 | 使用 ISP IP 的真实用户设备(台式机、手机) |
| 匿名性 | 高,但 IP 常被识别为数据中心 | 非常高,IP 看起来像正常用户 |
| 成本 | 通常更低 | 明显更高 |
| 速度 | 通常更快,延迟更低 | 可能更慢,延迟更高 |
| 被检测风险 | 被检测/封禁的风险更高 | 风险更低,适合绕过严格的反机器人措施 |
| 使用场景 | 通用抓取、公开数据、防护较弱的站点 | 高价值目标、电商、社交媒体、地域定向 |
法律与合规注意事项
robots.txt:遵守网站的robots.txt文件,其中规定了针对爬虫的规则。可通过http://example.com/robots.txt访问。- 服务条款:查看网站的服务条款,抓取可能被禁止。
- 数据使用:确保符合数据保护法规(例如 GDPR、CCPA)。
- 对服务器的影响:避免用大量请求压垮目标服务器,请设置适当的延迟。
