没接触过爬虫的人,一听到“网页数据录入Excel”,第一反应多半是复制、粘贴、再清洗,一条一条地弄。但凡是干过几次这种重复劳动,都会觉得不对劲:网页上明明有几百上千条数据,靠手工录到表格里,又慢又容易错。后来我给自己写了一套小工具,用Python把网页里的目标数据抓下来,再直接写进Excel,整个过程几分钟就跑完。今天把这个思路完整地拆开聊一聊,包含方案选型、代码实现、踩坑实录,让不写代码的同事看完也能动手试一把。
这个“网页数据录入Excel”的解决方案,本质上是三个环节的组合:抓取网页内容、解析筛选出需要的数据、把结构化结果写入Excel表格。需要的基础是懂一点点HTML标签、会安装Python库,不需要数学编程基础。适合四类人看:经常要看网页做报表的运营、电商选品和比价的人、被重复粘贴折磨的职场新人、想用代码解决日常问题但不知道从哪入手的开发者。
我这里用的组合是requests+BeautifulSoup+openpyxl,这也是这个场景里最稳妥、最省心的技术栈。下面按完整流程来讲。
1. 为什么选择“脚本解决”这条路
1.1 先看几个真实场景
先说个我遇到的典型需求。某运营同事负责每周整理竞品线上商城的商品价格和库存,光这一件事,每周要花掉大半天时间:打开网页、逐条复制名称、价格、上下架状态,再贴进Excel,还要顺手调整格式来保证表格干净。最麻烦的是,同样操作下周还要再做一遍,一点技术含量都没有,还容易在复制粘贴中漏掉一行。
类似的场景非常多:
- 从行业公示网站收集一批企业名单、编号、状态信息。
- 把电商平台某个分类下的商品名称、价格、链接整理成比价表。
- 从技术文档或新闻列表页批量获取标题、发布时间、摘要。
- 把网页表格(HTML table)里的数据整体转换为Excel表格。
这些场景有一个共同特点:数据已经存在于网页上,结构是规律的,只是没有一个直接的“下载Excel”按钮。所以真正需要的,不是一个复杂的大数据平台,而是一个能“把网页上的结构化信息搬进表格”的机械化流程。
1.2 复制粘贴为什么不是长久之计
手工复制粘贴最大的问题不是慢,而是不可控。你永远不知道自己是不是漏掉了某一行,也不知道网页加载出来的数据在粘贴后有没有发生错位。尤其是数据量超过几十条之后,肉眼很难逐条检查。
除此之外,重复性操作还带来一个隐藏成本:注意力消耗。人的精力放在机械操作上,就没法去思考数据本身的意义。用脚本代替重复劳动,并不是懒人想法,而是把时间留给真正需要判断的事,比如检查数据差异、分析价格走势、发现异常项。
1.3 方案选型:为什么是Python三件套
做网页数据采集,可选的技术路线其实不少。可以写浏览器插件,也可以用现成的图形化工具,还可以用Python写脚本。几个方案对比之后,我最终一直推荐“Python + requests + BeautifulSoup + openpyxl”这套组合,原因是:
requests负责网络请求,代码量极小,十几行就能拿到网页HTML源码。BeautifulSoup解析HTML的逻辑很贴近人的思维,可以像“在网页里找元素”一样写选择器,不用写复杂正则表达式。openpyxl直接操作.xlsx文件,能做样式、能设置列宽、能追加数据,不需要经过CSV中转。- 组合起来一共只有三个依赖库,环境搭建成本低,初学者也能在一个下午跑通。
也有人问,为什么不用Selenium这种自动化浏览器工具?这个后面会专门讲,这里先说结论:如果你的目标网页是纯静态的,打开就能看到数据,用Selenium属于“杀鸡用牛刀”,启动浏览器、加载页面带来的时间开销和稳定性问题都会被无限放大。
2. 动手前必须做好的准备工作
2.1 环境搭建与镜像源配置
开始写代码前,先把Python环境理顺。我建议从头建一个干净的虚拟环境,避免和系统里其他项目冲突。命令行窗口执行:
python -m venv venvWindows下激活虚拟环境:
venv\Scripts\activatemacOS/Linux下激活:
source venv/bin/activate然后安装三个库:
pip install requests beautifulsoup4 openpyxl如果下载速度不理想,可以临时指定一个国内PyPI镜像源,下载速度会快很多。这类镜像源在网上非常多,随便选一个稳定可用的即可。
注意:不要图省事直接
pip install bs4之后不装beautifulsoup4,这两个包名对应不同的发行版本。推荐直接安装beautifulsoup4,这样在脚本里from bs4 import BeautifulSoup才能正常工作。
2.2 网页结构基础:先看懂HTML再说
写爬虫脚本,本质上就是在HTML里“找东西”,所以起码要看得懂最基本的标签长什么样。一个最简单的网页条目,结构通常是这样的:
<div class="product-item"> <span class="title">无线鼠标</span> <span class="price">¥89.00</span> <a href="/detail/123">查看详情</a> </div>这里面涉及三个HTML核心概念:
- 标签(tag):
div、span、a这些尖括号包裹的标记。 - 属性(attribute):标签里的
class="product-item"就是属性,用来标记元素的类别或身份。 - 层级关系:外层
div里面包裹了多个子元素,这就是HTML的树状结构。
爬虫的解析逻辑,就是根据这些标签和属性,把目标数据“摘”出来。这个过程跟在超市里按货架标签找商品差不多:先定位到货架(外层容器),再拿想要的那几样(名称、价格、链接)。
2.3 用浏览器开发者工具定位目标元素
打开目标网页后,按F12打开开发者工具,使用左上角的“选取元素”按钮,点击页面上你想要的数据,右侧Elements面板会自动定位到对应的HTML代码。这时需要做的只是观察:
- 每条数据是不是都在同一个容器标签里?
- 名称、价格、链接分别用了什么class属性?
- 数据是在HTML源码里直接可见,还是需要等页面JS加载之后才出现?
最后这个问题特别关键,它直接决定了你用静态请求还是自动化浏览器。判断方法很简单:在Elements面板里看到的数据,如果右键“查看网页源代码”也能找到,就说明是静态数据;如果源代码里没有,只有Elements里能看到,那就是动态加载,静态方案要换思路。
3. 核心代码实现:从网页到Excel的完整流程
3.1 一次合格的网页请求长什么样
写爬虫最容易犯的错,是不带任何请求头直接requests.get(url),然后莫名其妙被拦截。很多网站不仅看你的IP,还看请求头里的User-Agent,一个普通浏览器的UA和一个Python脚本默认UA,服务器的回应完全不一样。
一个比较规范的请求是这样:
import requests headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9", } resp = requests.get("https://example.com/products", headers=headers, timeout=10) resp.raise_for_status() # 状态码不是200时抛出异常,避免继续往下执行 resp.encoding = resp.apparent_encoding # 自动识别网页编码,防中文乱码 html = resp.text这几个细节都有原因:
User-Agent改成浏览器的UA,是为了让服务器觉得“这是一个正常用户在访问”。timeout=10是网络超时限制,防止某个请求卡住导致整个脚本停摆。resp.encoding = resp.apparent_encoding是为了解决中文编码乱码,中文网页常见的编码有UTF-8和GBK,手动指定容易出错,让requests自动探测最省事。
3.2 解析HTML并提取数据
拿到HTML字符串之后,交给BeautifulSoup解析:
from bs4 import BeautifulSoup soup = BeautifulSoup(html, "html.parser") items = soup.select("div.product-item")soup.select()接收的是CSS选择器,这个语法对熟悉前端的人零成本。不熟悉也没关系,记住几种常用写法就行:
select("div"):选择所有div标签。select(".price"):选择所有class包含price的元素。select("#main"):选择id等于main的元素。select("div.product-item .title"):选择class为product-item的div里面的、class为title的所有元素。
拿到列表之后,逐条提取字段:
for item in items: title = item.select_one(".title") price = item.select_one(".price") link = item.select_one("a") if not title or not price: continue # 缺字段的就跳过,别硬处理 product_name = title.get_text(strip=True) product_price = price.get_text(strip=True) product_link = link.get("href", "") if link else ""select_one()只取第一个匹配元素,适合已知每个条目里只有一个目标字段的情况。get_text(strip=True)获取元素里的纯文本并去掉首尾空格,get("href", "")取链接属性,取不到就返回空字符串。
这里有个经验:解析的时候别写得太“死”。比如价格字段刚才还是£89.0,过两天网页结构改成₽89.0,那脚本就抓不到了。更稳妥的做法是只取字段内部的关键文本,不要硬编码完整的匹配文本。
3.3 数据清洗环节:不要原样写入
网页上提取出来的数据,很少是“干净”的。价格可能带货币符号,库存状态可能混着一堆空白字符,链接可能是相对路径。所以写入Excel之前,需要做一次简单的数据清洗。
拿价格举例,常见处理方式:
import re price_text = "¥ 89.00" price_clean = re.sub(r"[^\d.]", "", price_text) # 只保留数字和小数点 price_value = float(price_clean)链接如果是相对路径,要拼接成完整URL:
from urllib.parse import urljoin full_url = urljoin("https://example.com", product_link)清洗的目标不是把数据改得漂亮,而是让Excel里的数据格式统一,方便后面做筛选、排序、求和。如果带货币符号直接写入,你会发现Excel里“求和”根本算不出来。
3.4 用openpyxl写入Excel并处理格式
写入环节来看代码:
from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment wb = Workbook() ws = wb.active ws.title = "商品清单" ws.append(["商品名称", "价格(元)", "商品链接"]) for name, price, url in cleaned_data: ws.append([name, price, url])追加表头和数据都用ws.append(),它会自动在下一空行写入。但光写入还不够,一个让读者舒服的Excel至少应该做到三点:
- 表头加粗,底色加深,和数据区域区分开。
- 列宽根据内容自动调整。
- 价格列设置数字格式,单位为元,保留两位小数。
代码是这样的:
header_font = Font(color="FFFFFF", bold=True) header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") for cell in ws[1]: cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal="center") ws.column_dimensions["A"].width = 30 ws.column_dimensions["B"].width = 12 ws.column_dimensions["C"].width = 40 for row in ws.iter_rows(min_row=2, min_col=2, max_col=2): for cell in row: cell.number_format = "0.00" wb.save("网页数据.xlsx")注意:
ws[1]是第一行的所有单元格;column_dimensions设置列宽,需要手动指定合理的宽度,Excel没有“自动最适合列宽”的默认设置。
3.5 完整脚本:一套可以直接抄的流程
把前面的部分拼起来,就是一套从URL到Excel的完整脚本。我习惯把项目拆成三个文件:spider.py负责抓取解析,cleaner.py负责清洗,exporter.py负责写Excel。不过,对于一次性任务,写一个脚本文件完全够用。完整核心逻辑如下:
import re import requests from bs4 import BeautifulSoup from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment from urllib.parse import urljoin BASE_URL = "https://example.com/products" HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", } def fetch_html(url): resp = requests.get(url, headers=HEADERS, timeout=10) resp.raise_for_status() resp.encoding = resp.apparent_encoding return resp.text def parse_products(html): soup = BeautifulSoup(html, "html.parser") results = [] for item in soup.select("div.product-item"): title_el = item.select_one(".title") price_el = item.select_one(".price") link_el = item.select_one("a") if not title_el or not price_el: continue name = title_el.get_text(strip=True) price_text = price_el.get_text(strip=True) price_value = float(re.sub(r"[^\d.]", "", price_text)) href = link_el.get("href", "") if link_el else "" full_url = urljoin(BASE_URL, href) results.append([name, price_value, full_url]) return results def save_to_excel(data, filename="网页数据.xlsx"): wb = Workbook() ws = wb.active ws.title = "商品清单" ws.append(["商品名称", "价格(元)", "商品链接"]) for row in data: ws.append(row) header_font = Font(color="FFFFFF", bold=True) header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") for cell in ws[1]: cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal="center") ws.column_dimensions["A"].width = 30 ws.column_dimensions["B"].width = 12 ws.column_dimensions["C"].width = 60 for row in ws.iter_rows(min_row=2, min_col=2, max_col=2): for cell in row: cell.number_format = "0.00" wb.save(filename) if __name__ == "__main__": html = fetch_html(BASE_URL) clean_data = parse_products(html) save_to_excel(clean_data) print(f"完成,共写入 {len(clean_data)} 条数据")这段代码已经能覆盖绝大多数静态网页的数据采集需求。
4. 完整演示案例:从商品列表抓取到Excel
4.1 演示目标与关键分析
为了把前面几个章节串起来,我给一个真实的模拟案例,目标是一个商品列表页,页面上有多条商品信息,结构固定。我们要做的事情是:抓取所有商品名称、价格、链接,清洗后保存成一份带表头的Excel文件。
打开页面F12后,观察到以下结构:
<div class="product-item"> <span class="title">樱桃机械键盘</span> <span class="price">¥ 349.00</span> <a href="/goods/1001">详情</a> </div>这说明每条商品都在div.product-item里,名称在.title,价格在.price,链接在条目里的a标签。这就是前面代码里选择器写法的来源。
4.2 运行流程与结果验证
在命令行执行脚本:
python spider.py正常情况下输出:
完成,共写入 36 条数据打开生成的Excel文件,可以看到:
| 商品名称 | 价格(元) | 商品链接 |
|---|---|---|
| 樱桃机械键盘 | 349.00 | https://example.com/goods/1001 |
| 无线静音鼠标 | 89.00 | https://example.com/goods/1002 |
检查时不要只看条数,随手抽查三行数据,把Excel里的内容和网页实际内容比对一下。重点检查三处:价格小数位是否正确、链接是否完整、是否有空行混入。
4.3 如何把脚本泛化成通用工具
实际工作中,需求不大可能是“永远抓同一个页面”。更多情况是“换个页面再抓一遍”。所以脚本里的选择器和URL最好抽出来,作为配置项,放在脚本顶部的变量区域。
我一般会这样做:
CONFIG = { "url": "https://example.com/products", "container_selector": "div.product-item", "fields": { "name": ".title", "price": ".price", "link": "a", }, "excel_name": "商品清单.xlsx", }每次换新页面,只需要改CONFIG字典,改完后再跑一次,确认新页面的结构匹配。如果新页面结构差异太大,再微调选择器即可。
4.4 多页翻页抓取思路
网页数据如果分布在多页,可以做一个翻页的循环。常见翻页URL有两种形式:一是URL里有页码参数,二是点击“下一页”按钮触发请求。
URL页码形式的处理最简单:
for page in range(1, 6): page_url = f"https://example.com/products?page={page}" html = fetch_html(page_url) data.extend(parse_products(html)) time.sleep(1) # 每页之间延迟1秒,降低对目标站点的压力这里加time.sleep(1)不是磨蹭,而是基本的访问礼节。爬虫和普通浏览器的区别在于速度快,如果每秒钟请求几十个页面,服务器很容易判断为异常访问。主动降低请求频率,是避免被封号的最简单手段。
5. 那些年我们踩过的坑:常见问题排查实录
5.1 网页元素抓不到?可能是动态加载
最常遇到的坑是:用requests请求后,发现HTML源码里根本没有目标数据。这时候先在浏览器里看整个HTML源码,搜索目标字段,如果搜索不到,说明数据是通过JavaScript发请求后用脚本渲染上去的,静态请求拿不到完整内容。
这时的处理路径有三条:
- 找到页面背后的数据接口(往往是一个JSON API),直接请求接口拿数据,效率最高。
- 改用Selenium或Playwright做自动化浏览器,让真实浏览器渲染完成后再解析。
- 放弃页面渲染,直接在HTML里找服务器预埋的数据变量,有时也能挖到。
判断到底用哪条路径,取决于对方站点是否提供了一种不需要完整页面渲染的数据来源。很多网页为了前后端分离,都会有一个直接返回JSON的接口,找到这个接口,连BeautifulSoup都可以省掉。
5.2 中文乱码问题
中文乱码十有八九是编码判断错误。解决方案就是前面代码里那句:
resp.encoding = resp.apparent_encodingapparent_encoding是requests根据HTTP头、HTML 声明、内容统计推测出来的编码。多数情况下它是对的,但偶尔也会猜错。如果遇到中文乱码,可以自己打印resp.apparent_encoding值,再手动指定一次。比如网站明确是GBK编码:
resp.encoding = "gbk"5.3 被反爬限制:返回403或验证码
请求返回403,大多是反爬策略生效。优先检查是否缺少User-Agent。然后检查是否需要添加Referer,很多站点会校验请求来源,Referer必须是目标页面本身的域名才放行。再不行可以加Cookie,模拟一个已登录用户的状态。
反爬这块的重点是不做对抗,而是减少特征:
- 降低请求频率,加随机延迟。
- 随机化User-Agent,多备用几个浏览器UA。
- 遇到重要数据页面,优先找公开的数据接口。
注意:采集公共数据之前,一定先看目标网站的条款。本文讨论的是自动化办公场景下的数据整理,所有实践都应限定在允许访问的公开信息范围内。
5.4 写入Excel后数字格式不对
价格写入Excel后如果变成文本格式,求和时会出问题。根因是清洗环节没把字符串转成float,写入Excel的仍是文本。检查方式很简单:在Excel里点击该列单元格,看编辑栏显示的是数字还是文本。如果是文本,回到清洗代码,确认float()转换是否成功执行。
还有一种情况是价格字段里混入了逗号千分位,比如1,299.00,这时候用re.sub(r"[^\d.]", "", price_text)会把逗号删掉,变成1299.00,正好处理干净。
5.5 常见问题速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 返回403 | 缺请求头或触发反爬 | 补全UA/Referer,降低频率 |
| 中文乱码 | 编码判断错误 | resp.encoding = resp.apparent_encoding |
| 目标数据为空 | 动态加载 | 找JSON接口或改用Selenium |
| Excel里价格求和为0 | 价格是文本格式 | 清洗时float()数字格式化 |
| 链接只有相对路径 | 页面内链接是相对路径 | 用urljoin拼接完整URL |
| 某条数据缺字段报错 | 页面上有空条目 | 字段缺失时continue跳过 |
5.6 独家避坑技巧:先用本地文件调试
一个我常年使用的调试习惯:先不直接请求线上网页,而是把网页HTML源码保存到本地,先用本地文件跑通解析和写入逻辑。
with open("page.html", "r", encoding="utf-8") as f: html = f.read() soup = BeautifulSoup(html, "html.parser")这样做的价值在于:解析逻辑不会受到网络波动、反爬拦截的影响,出错时可以基于同一份HTML反复调试选择器。等本地解析完全正确了,再把逻辑接回requests,一次性跑通。尤其是选择器写不对的时候,这招能把调试时间压缩一半以上。
6. 进阶玩法:让这套方案更“程序员范儿”
6.1 用pandas替代手写清洗逻辑
当数据量变大、字段变多之后,逐条清洗和写入的手写逻辑会越来越繁琐。这时候可以引入pandas,把数据先组装成二维表,再一行代码导出Excel。
import pandas as pd df = pd.DataFrame(data, columns=["商品名称", "价格", "商品链接"]) df["价格"] = pd.to_numeric(df["价格"], errors="coerce") df.to_excel("网页数据.xlsx", index=False)pandas的优势在于表格处理能力:可以直接筛选、排序、去重、分组统计,甚至还能从多个网页抓来的数据做横向合并。适合需要做进一步分析的场景。
6.2 定时自动采集
脚本跑通之后,下一步自然是让它自动跑。Windows下可用任务计划程序,“触发器”设置每周或每天执行一次,“操作”指定需要执行的Python命令,就会在指定时间自动运行脚本并生成新的Excel文件。
macOS或Linux下用crontab:
0 9 * * 1 cd /path/to/project && /path/to/venv/bin/python spider.py上面的意思是每周一早上9点执行一次。固定时间自动采集的最大好处是,你只需要在数据真正变化的那一天打开Excel看结果,其他时间完全不用管。
6.3 处理动态网页的进阶方案:Selenium什么时候才用
强调一下,Selenium不是首选方案,而是兜底方案。只有在确认目标页面是动态渲染、且找不到数据接口的时候,才用Selenium。它启动浏览器、加载页面、点击翻页,真实模拟人的操作,能拿到完整渲染后的HTML。
用Selenium的简单示例:
from selenium import webdriver from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--headless") # 无界面模式,不弹浏览器窗口 driver = webdriver.Chrome(options=options) driver.get("https://example.com/products") html = driver.page_source driver.quit()拿到的HTML再交给BeautifulSoup解析,后续流程完全一样。但要清醒地认识到:Selenium的启动开销大、运行速度慢、对资源占用高,只适合少量动态页面。
6.4 这套方案的边界
这个方案能解决“网页数据录入Excel”,但并不适合所有场景。比如大规模爬取、需要登录的复杂系统、高度反爬的站点,这些都需要更专业的工程化手段,不在本文讨论范围内。
对手头这个需求——把网页上公开可见的结构化数据整理成Excel——脚本方案已经做到了“低成本、可复制、免重复劳动”这三件事,剩下的就是随用随改。
最后说点个人体会。我见过很多同事第一次跑通脚本时的表情:有点不敢相信网页数据真的自己进了Excel。但你真把这个流程跑通之后,会觉得它其实没什么高深的地方——拆解问题、选工具、写循环、调格式,每一步都是可以学习的经验。这个方案后续还能扩展成定时任务、邮件自动推送、图表自动生成,全看你的想象力。动手是第一步,别想太多,装好环境,复制这段代码,换上你自己的网页地址,跑一下试试。