在 Python 爬虫中,多线程是一种提升爬取效率的有效技术。在传统的单线程爬虫里,每次只能处理一个请求,只有等当前请求完成(包括发送请求、等待响应、解析数据)之后,才能开始下一个请求。而多线程爬虫可以让多个请求同时进行,在等待某个请求响应的时间里,CPU 可以去处理其他请求,充分利用了 CPU 时间,大大提高了爬取效率。
import requests
import threading
from bs4 import BeautifulSoup
requests
:用于发送 HTTP 请求,获取网页内容。threading
:Python 标准库,用于创建和管理线程。BeautifulSoup
:用于解析 HTML 和 XML 文档,方便提取网页中的数据。def crawl_url(url):
try:
response = requests.get(url)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
# 这里可以根据具体需求提取网页数据,例如提取所有链接
links = soup.find_all('a')
for link in links:
print(link.get('href'))
except requests.RequestException as e:
print(f"请求 {url} 时出错: {e}")
这个函数接收一个 URL 作为参数,发送 HTTP 请求获取网页内容,然后使用BeautifulSoup
解析网页,提取所有链接并打印出来。同时对请求异常进行了简单处理。
urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
]
threads = []
for url in urls:
thread = threading.Thread(target=crawl_url, args=(url,))
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
这里定义了一个 URL 列表,然后为每个 URL 创建一个线程,将crawl_url
函数作为线程的执行目标,URL 作为参数传入。启动所有线程后,使用join
方法等待所有线程执行完毕。
网站通常会有反爬虫机制,如 IP 封禁、验证码等。多线程爬虫由于请求频率高,更容易触发这些机制。可以采取以下措施应对:
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'
}
response = requests.get(url, headers=headers)
proxies = {
'http': 'http://proxy.example.com:8080',
'https': 'http://proxy.example.com:8080'
}
response = requests.get(url, proxies=proxies)
当多个线程同时访问和修改共享资源时,可能会出现数据不一致的问题。如果在多线程爬虫中需要共享数据(如存储爬取结果的列表),可以使用线程锁来保证线程安全。
import threading
# 创建一个锁对象
lock = threading.Lock()
results = []
def crawl_url(url):
global results
try:
response = requests.get(url)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a')
lock.acquire() # 获取锁
for link in links:
results.append(link.get('href'))
lock.release() # 释放锁
except requests.RequestException as e:
print(f"请求 {url} 时出错: {e}")
使用concurrent.futures
模块中的ThreadPoolExecutor
可以更方便地管理线程,避免手动创建和管理大量线程。
import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor
def crawl_url(url):
try:
response = requests.get(url)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
links = soup.find_all('a')
for link in links:
print(link.get('href'))
except requests.RequestException as e:
print(f"请求 {url} 时出错: {e}")
urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
]
# 创建线程池,最大线程数为5
with ThreadPoolExecutor(max_workers=5) as executor:
executor.map(crawl_url, urls)
ThreadPoolExecutor
会自动管理线程的创建、销毁和复用,map
方法会将crawl_url
函数应用到urls
列表中的每个 URL 上。这样可以更简洁地实现多线程爬虫,并且方便控制并发线程的数量。