技术博客-导航条

Python + BS4 爬虫实战:从零抓取农产品价格数据,搞定商业数据分析源

在数据分析、市场调研或创业规划中,商业数据的获取是第一步也是最关键的一步。农产品价格作为典型的商业数据,直接关系到供应链管理、定价策略等决策。但手动收集数据效率低下,而 Python 的 BeautifulSoup4(简称 BS4)库能轻松破解网页数据提取难题,比正则表达式更简洁、容错性更强,即使是爬虫新手也能快速上手。本文将以食品商务网的农产品价格数据为例,从环境搭建到实战爬取,再到问题排查和反爬突破,带你完整掌握 BS4 爬虫的核心技能。

一、为什么选择 BS4?爬虫新手的高效之选

在 BS4 出现之前,很多开发者会用正则表达式提取网页数据,但正则需要精准匹配 HTML 标签结构,一旦网页代码有微小变动,正则表达式就会失效,且编写过程繁琐易错。而 BS4 的核心优势的让它成为网页数据提取的首选工具:

  • 语法直观:无需复杂正则表达式,通过标签名、属性就能定位数据,符合 Python 简洁风格
  • 容错性强:即使网页代码不规范(如缺少闭合标签),也能正常解析
  • 解析灵活:支持 html.parser、lxml、html5lib 等多种解析器,可根据需求选择
  • 功能强大:支持嵌套查询、批量提取,能快速处理复杂 HTML 结构

对于商业数据爬取场景,BS4 能快速定位表格、列表中的目标数据,大幅降低爬虫开发门槛。

二、环境搭建:3 分钟搞定 BS4 与依赖库

爬取农产品价格数据需要用到 3 个核心库:BS4(解析网页)、requests(发送网络请求)、random(控制请求频率),安装步骤简单易懂:

1. 安装核心库

打开终端或命令提示符,输入以下命令一键安装:

bash

运行

pip install bs4 requests
  • bs4:核心解析库,用于提取网页数据
  • requests:用于向目标网站发送 HTTP 请求,获取网页源代码
  • 安装成功后,会自动关联依赖的 beautifulsoup4 和 soupsieve 库,无需额外操作

2. 验证安装

安装完成后,在 Python 交互式环境中输入以下代码,无报错则说明安装成功:

python

运行

from bs4 import BeautifulSoup
import requests
import random
print("环境搭建成功")

三、实战爬取:农产品价格数据抓取完整流程

本次爬取目标是食品商务网的果蔬价格数据(网址:https://price.21food.cn/guoshu-p{n}.html),数据包含产品名称、规格、平均价格、日期、价格趋势等商业核心信息。

1. 爬取思路拆解

  1. 分析网页结构:目标数据存储在<ul>标签下的<table>表格中,每行数据对应一个<tr>标签,列数据对应<td>标签
  2. 构造翻页 URL:网页翻页通过 URL 中的p{n}参数控制(n 为页数),如第 1 页是 p1,第 2 页是 p2
  3. 发送请求:携带浏览器伪装信息(User-Agent),避免被网站识别为爬虫
  4. 解析数据:用 BS4 定位目标标签,提取产品名称、价格等信息
  5. 数据保存:将提取的数据写入 CSV 文件,方便后续数据分析
  6. 控制频率:添加随机延迟,降低反爬风险

2. 完整代码实现

python

运行

import time
import random
import requests
from bs4 import BeautifulSoup

# 打开CSV文件,准备写入数据
with open("农产品价格数据.csv", mode="w", encoding="utf-8") as f:
    # 写入CSV表头
    f.write("产品名称,规格,平均价格,日期,价格趋势\n")
    total_data = 0  # 统计总数据量
    page = 1  # 起始页数
    max_page = 29  # 目标总页数

    while page <= max_page:
        # 构造当前页URL
        url = f"https://price.21food.cn/guoshu-p{page}.html"
        
        # 浏览器伪装:模拟真实用户访问,避免被反爬
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
        }
        
        try:
            # 发送HTTP请求,获取网页源代码
            response = requests.get(url, headers=headers)
            response.encoding = "utf-8"  # 设置编码,避免中文乱码
            
            # 初始化BS4对象,解析网页
            soup = BeautifulSoup(response.text, "html.parser")
            
            # 定位数据所在的核心标签:先找到所有ul标签
            ul_list = soup.find_all("ul")
            current_page_data = 0  # 统计当前页数据量
            
            # 遍历ul标签,提取表格数据
            for ul in ul_list:
                # 找到ul下的所有表格
                table_list = ul.find_all("table")
                for table in table_list:
                    # 找到表格中的行数据
                    tr = table.find("tr")
                    tds = tr.find_all("td")
                    
                    # 关键判断:只处理包含5列及以上数据的表格(避免数据缺失报错)
                    if len(tds) >= 5:
                        # 提取各列数据,strip()去除多余空格和换行符
                        product_name = tds[0].text.strip()
                        spec = tds[1].text.strip()
                        avg_price = tds[2].text.strip()
                        date = tds[3].text.strip()
                        trend = tds[4].text.strip() if tds[4].text.strip() else "无"
                        
                        # 写入CSV文件
                        f.write(f"{product_name},{spec},{avg_price},{date},{trend}\n")
                        current_page_data += 1
                        total_data += 1
            
            # 打印当前页爬取结果
            print(f"第{page}页爬取完成,共获取{current_page_data}条数据")
            
            # 随机延迟1-2秒,模拟人类浏览行为,降低反爬风险
            time.sleep(random.uniform(1, 2))
            
            # 页数递增,进入下一页
            page += 1
        
        except Exception as e:
            print(f"第{page}页爬取失败,错误信息:{str(e)}")
            # 失败后延迟3秒再重试
            time.sleep(3)

# 爬取完成提示
print(f"全部爬取任务结束!共获取{total_data}条农产品价格数据")

3. 代码关键细节解析

  • 浏览器伪装:headers中的User-Agent字段模拟 Chrome 浏览器,避免网站直接拒绝爬虫请求
  • 数据校验:if len(tds) >= 5是核心容错逻辑,防止因网页中存在非目标表格(如仅含 2-3 列的广告表格)导致索引报错
  • 编码设置:response.encoding = "utf-8"确保中文数据正常显示,避免乱码
  • 随机延迟:random.uniform(1, 2)生成 1-2 秒的随机延迟,降低请求频率,减少被反爬的概率

四、新手必踩坑:翻页爬取与反爬突破

在实战过程中,很多新手会遇到翻页失败、爬取中断等问题,以下是针对本次爬取场景的重点问题解析和解决方案:

1. 翻页爬取失败:IndexError 报错

问题现象

爬取第 1 页后直接报错IndexError: list index out of range,程序终止,无法进入下一页。

问题根源

网页中除了包含目标数据的 5 列表格,还存在其他结构的表格(如 3 列的市场信息表格),当代码尝试访问tds[4](第 5 列数据)时,因表格列数不足导致索引越界。

解决方案

添加表格列数校验if len(tds) >= 5,只处理符合目标结构的表格,跳过无效表格,确保程序正常执行到翻页逻辑。

2. 反爬限制:仅能爬取 5 页数据

问题现象

爬取前 5 页数据正常,从第 6 页开始无法获取数据(显示 0 条),甚至出现滑块验证页面。

反爬机制分析

目标网站通过两种方式限制爬虫:

  • IP 识别:网站会记录公网 IP 的访问频率,频繁访问会被标记为爬虫,限制数据返回
  • JS 环境检测:网站通过 JavaScript 检测用户行为(如鼠标移动、滚动操作),爬虫因未执行 JS 被识别

突破方案

  1. 动态切换 IP:使用代理池 IP(类似 “快递中转站”,隐藏真实 IP),推荐使用付费代理服务(如阿布云、快代理),代码修改示例:

python

运行

# 添加代理IP配置
proxies = {
    "http": "http://代理IP:端口",
    "https": "https://代理IP:端口"
}
# 发送请求时添加代理参数
response = requests.get(url, headers=headers, proxies=proxies)
  1. 模拟 JS 执行:使用 Selenium 替代 requests,模拟浏览器的鼠标移动、滚动等行为,让爬虫更像真实用户,代码示例:

python

运行

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains

# 初始化Chrome浏览器
driver = webdriver.Chrome()
driver.get(url)

# 模拟鼠标移动
action = ActionChains(driver)
action.move_by_offset(100, 200).perform()  # 鼠标移动到坐标(100,200)
time.sleep(1)

# 模拟滚动
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
time.sleep(1)

# 获取执行JS后的网页源代码
page_source = driver.page_source
soup = BeautifulSoup(page_source, "html.parser")
  1. 降低访问频率:将随机延迟调整为 2-5 秒,进一步减少 IP 被封禁的风险

五、扩展应用:从数据爬取到可视化分析

爬取到的 CSV 数据可直接用于商业分析,以下是简单的扩展应用示例:

1. 数据查看与清洗

用 pandas 库快速查看数据结构,处理缺失值:

python

运行

import pandas as pd

# 读取CSV数据
df = pd.read_csv("农产品价格数据.csv")
# 查看数据前5行
print(df.head())
# 查看数据基本信息(行数、列数、数据类型)
print(df.info())
# 处理缺失值(将"无"替换为NaN,方便后续分析)
df["价格趋势"] = df["价格趋势"].replace("无", pd.NA)

2. 图片爬虫扩展

如果需要爬取农产品图片(如产品展示图),可在原有代码基础上添加图片下载逻辑:

python

运行

# 提取图片URL(假设图片在td[5]的img标签中)
img_tag = tds[5].find("img")
if img_tag:
    img_url = img_tag.get("src")
    # 补全图片URL(如果是相对路径)
    if not img_url.startswith("http"):
        img_url = "https://price.21food.cn" + img_url
    # 下载图片
    img_response = requests.get(img_url, headers=headers)
    with open(f"农产品图片/{product_name}.jpg", mode="wb") as img_f:
        img_f.write(img_response.content)  # 以二进制形式写入图片

六、总结:BS4 爬虫的商业价值与学习方向

通过本次实战,我们用 BS4 成功爬取了农产品价格商业数据,验证了 BS4 在网页数据提取中的高效性。对于商业场景而言,BS4 爬虫可广泛应用于竞品价格监控、市场趋势分析、供应链数据收集等场景,为决策提供数据支撑。

新手学习建议:

  1. 熟练掌握find()find_all()方法,这是 BS4 定位数据的核心
  2. 重视容错逻辑编写,实际网页结构复杂多变,需提前预判可能的异常
  3. 了解常见反爬机制(IP 封禁、JS 检测、请求频率限制),掌握对应的突破方法
  4. 结合数据分析库(如 pandas、matplotlib),让爬取的数据产生实际价值

后续可深入学习的方向:XPath 解析、Selenium 动态爬虫、代理池搭建等,进一步提升爬虫的稳定性和适用范围。