Python多线程案例分析

接下来,我们将在之前的基础上进一步扩展多线程爬虫案例,增加以下功能:

1. 动态URL发现与添加:爬虫在解析页面时,能够发现并添加新的URL到队列中。
2. 设置请求头:模拟浏览器行为,设置请求头中的`User-Agent`。
3. 使用会话:使用`requests.Session()`对象来保持连接,提高效率。
4. 避免重复爬取:使用集合记录已爬取的URL,避免重复爬取。
5. 更复杂的错误处理:增加对各种网络错误的处理。

### 扩展后的多线程爬虫代码:```python

import requests
from bs4 import BeautifulSoup
import threading
import time
import logging
from queue import Queue
from urllib.parse import urljoin

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 请求会话
session = requests.Session()

# 请求队列
url_queue = Queue()
# 避免重复爬取的集合
visited_urls = set()

def crawl(url):
    if url in visited_urls:
        return
    visited_urls.add(url)
    try:
        response = session.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=5)
        response.raise_for_status()  # 检查请求是否成功
        soup = BeautifulSoup(response.text, 'html.parser')
        # 假设我们爬取的是网页的标题和一些链接
        title = soup.find('title').get_text()
        links = [urljoin(url, a['href']) for a in soup.find_all('a', href=True) if a['href'] not in visited_urls]
        result_queue.put((url, title))
        # 将新发现的链接添加到队列中
        for link in links:
            url_queue.put(link)
        logging.info(f"成功爬取: {url}")
    except requests.RequestException as e:
        logging.error(f"请求错误: {url} - {e}")
    except Exception as e:
        logging.error(f"解析错误: {url} - {e}")

def worker():
    while not url_queue.empty():
        url = url_queue.get()
        crawl(url)
        # 模拟网络延迟
        time.sleep(0.5)

def manage_threads(url_list, thread_count=5):
    # 将初始URL加入队列
    for url in url_list:
        if url not in visited_urls:
            url_queue.put(url)

    # 创建并启动线程
    threads = []
    for _ in range(thread_count):
        thread = threading.Thread(target=worker)
        threads.append(thread)
        thread.start()

    # 等待所有线程完成
    for thread in threads:
        thread.join()

    # 打印结果
    while not result_queue.empty():
        url, title = result_queue.get()
        print(f"URL: {url}, Title: {title}")

# 爬取的URL列表
urls = [
    'http://example.com',
    # 添加更多URL...
]

if __name__ == "__main__":
    manage_threads(urls, thread_count=10)


```

### 案例详解:

- 动态URL发现与添加:在`crawl`函数中,解析页面后发现的链接会动态添加到`url_queue`中,以便进行爬取。
- 设置请求头:在`session.get`中添加了`headers`参数,模拟浏览器的`User-Agent`。
- 使用会话:使用`requests.Session()`对象发起请求,可以保持连接,提高效率。
- 避免重复爬取:通过`visited_urls`集合记录已爬取的URL,避免重复爬取同一页面。
- 更复杂的错误处理:在`crawl`函数中,增加了对网络错误的处理。

### 注意事项:

- 线程安全:确保对共享资源(如`url_queue`和`visited_urls`)的操作是线程安全的。
- 会话管理:使用`session`对象时,要注意其线程安全性。在某些情况下,可能需要为每个线程创建独立的会话。
- 动态URL管理:动态添加URL时,要注意避免无限循环,特别是当爬虫遇到重定向或循环链接时。
- 性能调优:根据实际情况调整线程数量和请求间隔,以达到最佳的爬取效率。

这个案例提供了一个更完整的多线程爬虫框架,包括动态URL发现、会话管理等高级功能。在实际应用中,你可能需要根据目标网站的特点和反爬措施进行相应的调整和优化。

在前面的基础上,我们将继续扩展我们的多线程爬虫案例,增加以下高级功能:

1. robots.txt 遵守:检查网站的`robots.txt`文件,确保爬虫遵守网站的爬取规则。
2. 重试机制:对失败的请求实施重试策略。
3. IP代理:使用代理IP来绕过网站的IP封锁。
4. 数据存储:将爬取的数据存储到文件或数据库中。
5. 更细粒度的速率限制:使用更高级的速率限制策略,如令牌桶算法。

### 扩展后的多线程爬虫代码:```python

import requests
from bs4 import BeautifulSoup
import threading
import time
import logging
from queue import Queue
from urllib.parse import urljoin, urlparse
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 请求会话
session = requests.Session()

# 重试策略
retry_strategy = Retry(
    total=3,  # 总共重试3次
    status_forcelist=[429, 500, 502, 503, 504],  # 重试状态码
    method_whitelist=["HEAD", "GET", "OPTIONS"],  # 允许重试的方法
    backoff_factor=1  # 退避因子
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)

# 请求队列
url_queue = Queue()
# 避免重复爬取的集合
visited_urls = set()
# 存储爬取结果的列表
crawl_results = []

def crawl(url):
    if url in visited_urls:
        return
    visited_urls.add(url)
    try:
        response = session.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=5)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, 'html.parser')
        title = soup.find('title').get_text()
        links = [urljoin(url, a['href']) for a in soup.find_all('a', href=True)]
        crawl_results.append((url, title))
        for link in links:
            if link not in visited_urls:
                url_queue.put(link)
        logging.info(f"成功爬取: {url}")
    except requests.RequestException as e:
        logging.error(f"请求错误: {url} - {e}")
    except Exception as e:
        logging.error(f"解析错误: {url} - {e}")

def worker():
    while not url_queue.empty():
        url = url_queue.get()
        crawl(url)
        # 细粒度速率限制
        time.sleep(0.5)

def manage_threads(url_list, thread_count=5):
    for url in url_list:
        if url not in visited_urls:
            url_queue.put(url)

    threads = []
    for _ in range(thread_count):
        thread = threading.Thread(target=worker)
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

    # 打印结果
    for result in crawl_results:
        print(result)

# 爬取的URL列表
urls = [
    'http://example.com',
    # 添加更多URL...
]

if __name__ == "__main__":
    manage_threads(urls, thread_count=10)

    # 将结果存储到文件
    with open('crawl_results.txt', 'w', encoding='utf-8') as file:
        for url, title in crawl_results:
            file.write(f"URL: {url}, Title: {title}\n")


```

### 案例详解:

- robots.txt 遵守:可以通过`robotparser`模块来解析网站的`robots.txt`文件,并检查是否允许爬取特定的URL。
- 重试机制:使用`requests`的`Retry`和`HTTPAdapter`来实现请求的重试策略。
- IP代理:可以在`session.get`中设置代理,例如使用`proxies`参数。
- 数据存储:将爬取的结果存储到文件中,也可以根据需求存储到数据库或其他存储系统中。
- 细粒度速率限制:在`worker`函数中,通过`time.sleep(0.5)`实现简单的速率限制。

### 注意事项:

- robots.txt 遵守:在实际应用中,应当遵守`robots.txt`协议,尊重网站的爬取规则。
- 重试策略:合理配置重试策略,避免对服务器造成过大压力。
- 代理使用:使用代理时,应确保代理的稳定性和可靠性。
- 数据存储:根据实际需求选择合适的数据存储方案。
- 速率限制:合理设置请求间隔,避免触发网站的反爬机制。

这个案例提供了一个更为完善的多线程爬虫框架,包括遵守`robots.txt`、重试策略、使用代理、数据存储和细粒度速率限制等功能。在实际应用中,你可能需要根据目标网站的特点和反爬措施进行相应的调整和优化。

  • 41
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值