2026 Python采集实战:BeautifulSoup批量抓取多页电商商品数据
单页商品抓取不难,真正容易出问题的是从第1页跑到第50页。 我做采集这些年遇到过不少类似情况:前几页运行正常,到了中间突然超时;某一页结构变化,整个程序直接退出;代理已经失效,脚本却还在对同一个地址反复重试。 所以,多页采集不能只在单页代码外面套一个`for`循环。至少要补上会话复用、有限重试、随机间隔、异常记录和CSV存储。 本文仍使用公开爬虫练习网站演示。实际采集电商平台前,应确认访问授权、服务条款和数据使用边界。 ## **先建立可复用的请求会话** 如果每一页都重新创建连接,会增加额外开销。`requests.Session()`可以复用部分连接及公共请求头: ``` import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session = requests.Session() session.headers.update({ "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/126.0.0.0 Safari/537.36" ), "Accept-Language": "zh-CN,zh;q=0.9" }) retry = Retry( total=2, connect=2, read=2, status=2, backoff_factor=0.8, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET"] ) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) ``` 重试次数不是越多越好。我一般先设为2次,用于处理短暂网络波动。遇到403、登录提示或验证码时,应停止并检查权限与访问策略,而不是通过无限重试继续施压。 ## **编写多页商品解析逻辑** 完整示例代码如下: ``` import csv import random import time from urllib.parse import urljoin import requests from bs4 import BeautifulSoup from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry BASE_URL = "https://books.toscrape.com/catalogue/page-{}.html" OUTPUT_FILE = "ecommerce_products.csv" session = requests.Session() session.headers.update({ "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/126.0.0.0 Safari/537.36" ) }) retry = Retry( total=2, backoff_factor=0.8, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["GET"] ) adapter = HTTPAdapter(max_retries=retry) session.mount("http://", adapter) session.mount("https://", adapter) def fetch_page(page_number): page_url = BASE_URL.format(page_number) response = session.get( page_url, timeout=(5, 15) ) response.raise_for_status() soup = BeautifulSoup(response.text, "html.parser") cards = soup.select("article.product_pod") products = [] for card in cards: title_tag = card.select_one("h3 a") price_tag = card.select_one(".price_color") stock_tag = card.select_one(".instock.availability") if not title_tag or not price_tag: continue products.append({ "页码": page_number, "商品名称": title_tag.get("title", "").strip(), "价格": price_tag.get_text(strip=True), "库存状态": ( stock_tag.get_text(" ", strip=True) if stock_tag else "" ), "详情链接": urljoin( page_url, title_tag.get("href", "") ) }) return products def save_products(products): fieldnames = [ "页码", "商品名称", "价格", "库存状态", "详情链接" ] with open( OUTPUT_FILE, "w", newline="", encoding="utf-8-sig" ) as file: writer = csv.DictWriter( file, fieldnames=fieldnames ) writer.writeheader() writer.writerows(products) def main(): all_products = [] failed_pages = [] for page_number in range(1, 11): try: page_products = fetch_page(page_number) all_products.extend(page_products) print( f"第{page_number}页完成," f"获取{len(page_products)}条" ) except requests.RequestException as error: failed_pages.append(page_number) print(f"第{page_number}页失败:{error}") time.sleep(random.uniform(1.5, 3.0)) save_products(all_products) print(f"共保存{len(all_products)}条商品数据") print(f"失败页码:{failed_pages}") if __name__ == "__main__": main() ``` 这段代码有一个比较实用的设计:某一页失败后,只记录失败页码,不会让整个任务立即退出。后续可以单独补采失败页面,而不必把成功页面全部重跑一遍。 ## **为什么要保留页码字段** 保存结果时,我特意增加了“页码”字段。 商品名称、价格和链接是业务数据,页码则是采集链路数据。出现数量异常时,可以直接查看是哪一页缺失。如果不保存页码,最后只知道总量少了,却很难快速定位断点。 正式任务中还可以继续增加: - 采集时间; - HTTP状态码; - 请求耗时; - 出口标识; - 重试次数。 这些字段未必都要交付给业务人员,但对排查稳定性很有帮助。 ## **代理IP应该在哪一步接入** 如果只是抓取练习网站的10个页面,没有必要为了使用代理而使用代理。 当任务扩大到大量授权页面、不同地区商品展示验证或持续价格监测时,才需要把请求出口纳入设计。我一般会把代理认证地址写入环境变量: ``` import os proxy_url = os.getenv("PROXY_URL") if proxy_url: session.proxies.update({ "http": proxy_url, "https": proxy_url }) ``` 接入极安代理时,也可以沿用这种方式。代理地址变化后只调整运行环境,不改采集逻辑,更适合长期维护。 不过,代理不能替代访问授权,也不能解决所有失败。403可能是权限或页面策略问题,429通常与请求频率有关,连接超时才更需要检查代理存活状态、认证配置和链路响应。 ## **别一开始就把并发调得很高** 不少人为了“提速”,单线程代码刚跑通就直接开20个线程。结果目标站响应变慢、429增加、代理连接数也被打满,最终实际完成速度反而下降。 我的习惯是先用低并发跑一小批页面,记录成功率和平均响应时间,再逐步增加任务量。每次只改一个参数,才能判断性能变化来自哪里。 对于BeautifulSoup多页采集来说,能稳定完成、失败可定位、结果可补采,比短时间内冲出一个很高的请求数更重要。先把单页解析做准,再把多页链路做稳,最后才轮到并发和代理优化。