精进Beautiful Soup 小技巧(三)---综合提供效率(缓存/error/多线程/异步)

前言:

提高抓取和解析效率的根本还是在于发送请求;如何从这个方面进行效率提升呢?

深入使用requests.Session()

深入使用requests.Session()

1.持久连接:

当使用 requests.Session() 时,连接会话中所有的请求将优先使用一个TCP连接,即"持久连接",这样即使你发起多次对同一主机的独立请求,Session 实例会重用底层的连接,从而降低握手的开销。
python 复制代码
import requests

# 创建一个会话实例
session = requests.Session()

# 向相同的主机发送多次请求
response_one = session.get('https://httpbin.org/get')
response_two = session.get('https://httpbin.org/get')

# 展示使用了持久连接的行为,两个请求将通过相同的连接发送
print(id(response_one.raw._connection))
print(id(response_two.raw._connection))

# 输出一样的ID,意味着使用的是同一连接

# 事后一定清理:常规操作
session.close()

2.连接适配和参数预设:

Session 对象允许你自定义一些请求细节,如头信息和鉴权凭证等,并在之后的请求中保持这些设置,减少了重复代码的编写。
python 复制代码
import requests
from requests.auth import HTTPBasicAuth

# 创建会话实例,并设置默认值
session = requests.Session()
session.headers.update({'user-agent': 'my-app/0.0.1'})
session.auth = HTTPBasicAuth('username', 'password')

# 现在进行的所有的请求都会发送预设的头信息
response = session.get('https://httpbin.org/headers')
print(response.text)  # 应当会见到"user-agent"和之前设定的鉴权信息

# 一般在完成请求后关闭会话
session.close()

3.为请求维持Cookie状态:

Session 对象自动处理请求的 Cookies,所有发给同一个会话的请求将使用同一个Cookie jar,在这样的机制下,所有与server的会话变量都可以一次设立,然后按预期工作。
python 复制代码
import requests

# 创建会话实例
session = requests.Session()

# 初次登录以设置cookie
login_res = session.post('https://example.com/login', data={'username':'xxx', 'password':'yyy'})

# Session会保存服务端设置在客户端的cookie信息, 现在进行的请求都将携带这个cookie
profile_res = session.get('https://example.com/profile')

# 经过验证的响应内容
print(profile_res.text)

# 完成所有动作后关闭会话提释放资源
session.close()

你现在应该有了一个清晰的Session如何作为一个持久连接来降低延时的认识,如何使用Session预设请求参数和身份验证方式,以及如何维持cookies的状态以跨请求进行身份维持和通行。在所有会话结束之后,确保调用 .close() 方法至关重要,以确保资源的妥善释放。

异常处理

网络爬虫可能面临各种预料之外的问题,如网络波动、页面结构更改、服务器配置问题等。为了提高脚本的健壮性,应当合理捕获并处理这些异常。

案例1:处理网络请求异常

python 复制代码
import requests
import logging
from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException

# 配置logging,设置日志级别为WARNING,简短日志格式,并将日志输出到控制台。
logging.basicConfig(level=logging.WARNING, 
                    format='%(asctime)s - %(levelname)s - %(message)s')

url = "https://example.com"

try:
    response = requests.get(url, timeout=10)
    response.raise_for_status()

except HTTPError as http_err:
    logging.warning(f"HTTP错误发生了:{http_err}")
except ConnectionError as conn_err:
    logging.warning(f"连接错误发生了:{conn_err}")
except Timeout as timeout_err:
    logging.warning(f"请求超时了:{timeout_err}")
except RequestException as err:
    logging.warning(f"出现了请求错误:{err}")
else:
    print("请求成功完成。")

案例2:处理Beautiful Soup可能的异常

python 复制代码
from bs4 import BeautifulSoup
import logging

# 配置logging,设置日志级别并输出到控制台
logging.basicConfig(level=logging.WARNING, 
                    format='%(asctime)s - %(levelname)s - %(message)s')

html_doc = """
<html><title>This is title</title></html>
"""

try:
    soup = BeautifulSoup(html_doc, "html.parser")
    title_text = soup.title.text

except AttributeError as e:
    # 如果BeautifulSoup尝试访问不存在的属性会抛出这个错误
	logging.warning(f"未能找到属性。错误:{e}")

except Exception as e:
    # 通用异常捕获,可能在解析HTML文档时遇到其他没有预料到的错误
	logging.error(f"发生错误:{e}")

else:
    print(f"文档的标题是:{title_text}")
对于Beautiful Soup,在操作前应检查返回对象是否为预期的标签,可以简单通过条件语句实现,例如:if soup.title:
尝试将异常处理模块化,以便在多处爬虫代码中重复使用。例如,可能为网络请求定义一个函数,并以此处理所有网络请求相关的异常。
针对预期可能发生的错误,可以定义明确的异常处理逻辑,如网络信号弱时重试操作等。
最重要的是编写清晰、易读且易于维护的代码,异常处理也要紧密跟随这个准则。

使用多线程和并发

当处理的网页数量庞大时,这一过程往往相当耗时。在Python中通过threading和concurrent.futures模块将Beautiful Soup的使用并行化,显著提升效率。

多线程基础

threading模块允许我们运行多个线程(即任务)来执行代码。在网络请求和HTML解析任务中,多线程能有效减少等待I/O操作(如网络请求)的时间。

使用concurrent.futures简化多线程

concurrent.futures模块提供了一种高级别的异步执行机制,通过ThreadPoolExecutor类我们可以非常方便地创建线程池。

案例一:简单多线程HTML请求和解析

我们首先摆脱繁杂的线程管理,并且用concurrent.futures来提升我们代码的执行速度:
python 复制代码
from concurrent.futures import ThreadPoolExecutor
import requests
from bs4 import BeautifulSoup

urls = [
  'https://example.com',
  'https://example.org',
  'https://example.net',
]

def fetch_and_parse(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    title = soup.title.text
    return title

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = [executor.submit(fetch_and_parse, url) for url in urls]
    for future in concurrent.futures.as_completed(futures):
        try:
            data = future.result()
            print(data)
        except Exception as exc:
            print(f"生成异常: {url} {exc}")
在这个案例中,ThreadPoolExecutor创建了一个线程池,异步地请求网页并解析标题标签

案例二:并发实现细粒度Html元素处理

如果网页数据解析涉及大量细致的处理,我们进一步地将Html元素的收集和处理分摊到不同线程去执行。
python 复制代码
from concurrent.futures import ThreadPoolExecutor
import requests
from bs4 import BeautifulSoup

url = "https://example.com/products"

def parse_html(html):
    soup = BeautifulSoup(html, "html.parser")
    products = soup.find_all('li', {'class': 'product'})
    return [product.text for product in products]

def get_html(url):
    response = requests.get(url)
    return response.text

with ThreadPoolExecutor() as executor:
    html = executor.submit(get_html, url).result()
    product_texts = executor.submit(parse_html, html).result()

print(product_texts)
executor.submit()负责提交任务给线程池,此处分别用独立的线程下载HTML文档和解析文档中的产品列表。

案例三:避免全局解释器锁(GIL)带来的影响

虽然threading在I/O密集型任务中表现良好,但GIL(Global Interpreter Lock,全局解释器锁)可能会在某些情况下影响效率。此时,我们可以考虑使用 Python 的 multiprocessing 模块。
python 复制代码
from multiprocessing.pool import ThreadPool
import requests
from bs4 import BeautifulSoup

urls = ["https://example.com", "https://example.org"]

def fetch_and_parse(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    return soup.title.text

if __name__ == '__main__':
    pool = ThreadPool(processes=2)
    results = pool.map(fetch_and_parse, urls)
    pool.close()
    pool.join()
    for title in results:
        print(title)
通过创建一个基于进程的ThreadPool来完成并发执行,这样就可以绕过GIL的限制,适用于任何数目的cpu密集型和I/O密集型任务。

运用多线程和并发可以大量缩短网页数据处理的时间,对于领域内从事数据采集和分析的从业者来说,这是提升工作效率的重要方法。希望通过本文,您能利用Python提供的并发工具,更高效地实现爬虫和数据解析任务。

相关推荐
七夜zippoe8 小时前
CANN Runtime任务描述序列化与持久化源码深度解码
大数据·运维·服务器·cann
盟接之桥8 小时前
盟接之桥说制造:引流品 × 利润品,全球电商平台高效产品组合策略(供讨论)
大数据·linux·服务器·网络·人工智能·制造
Fcy6489 小时前
Linux下 进程(一)(冯诺依曼体系、操作系统、进程基本概念与基本操作)
linux·运维·服务器·进程
袁袁袁袁满10 小时前
Linux怎么查看最新下载的文件
linux·运维·服务器
代码游侠10 小时前
学习笔记——设备树基础
linux·运维·开发语言·单片机·算法
主机哥哥10 小时前
阿里云OpenClaw部署全攻略,五种方案助你快速部署!
服务器·阿里云·负载均衡
Harvey90310 小时前
通过 Helm 部署 Nginx 应用的完整标准化步骤
linux·运维·nginx·k8s
珠海西格电力科技11 小时前
微电网能量平衡理论的实现条件在不同场景下有哪些差异?
运维·服务器·网络·人工智能·云计算·智慧城市
释怀不想释怀11 小时前
Linux环境变量
linux·运维·服务器
zzzsde11 小时前
【Linux】进程(4):进程优先级&&调度队列
linux·运维·服务器