上一篇你抓下了三行小表,算是爬虫入门了;今天上强度:面对一张带表头、几十行、好几列的”真表格”,让程序把表头和数据一格不落地搬下来,还能顺手存成 CSV。手艺还是那套(find + get_text),变的只是”按图索骥”的图更复杂一点。
一、从”三行小表”到”一张大表”
先说今天要解决的真实麻烦。
上一篇的值班表只有三行两列,说难听点,复制粘贴也就十秒钟。但你在工作里真正会碰到的表长这样:学校网站上的全校课程表(几十行、星期一到星期五五六列)、政务网站上的补贴发放公示表(上百行、姓名/金额/银行卡号七八列)、购物网站上的参数对比表(十几行、三个产品列)。
这种表,复制粘贴就不是十秒钟的事了——粘错了行、漏了列、表头和数据对不上,是常有的事。更要命的是它每周都更新,你每周都得重粘一遍。
今天的目标很明确:写一个程序,运行一下,一整张大表原封不动地变成一个 CSV 文件——表头一行不少,数据一行不落,列和列对得齐齐整整。
先给你吃颗定心丸:今天不引入新的库。requests、BeautifulSoup、csv 都是前41篇的老朋友。今天唯一的”新知识”只有一句话:一张真正的表格里,表头和数据是分开住的,抓的时候要分开抓,再拼起来。就这一句。剩下的全是手艺的重新组合——你甚至可以说,今天是”旧手艺的实战演练日”。
二、先看懂”真表格”的解剖图
上一篇的小表长这样(简化版):
周一 张三
而真实的表格长这样:
日期 值班人 班次 联系电话
10-19 张三 早班 138****0001
10-20 李四 晚班 138****0002
10-21 王五 早班 138****0003
对比一下,多了三样东西,今天全靠它们干活:
和:表格分成了”表头区”和”数据区”,就像一张 Excel 表的第一行是标题、下面才是数据。thead里装标题行,tbody里装数据行。:表头单元格(table header),专门装”这一列叫什么”。数据单元格还是老朋友 。th 是标签,td 是内容——一个是列名,一个是值。 - 行变多了、列变多了:每行从 2 个格子变成 4 个格子,
从 3 行变成 N 行。 用人话说:上一篇的表是”一张便签纸”,今天的表是”一张真正的 Excel”。Excel 有标题行、有数据区、有列名——HTML 表格也一样,就是用
thead/th和tbody/td表达的。类比记忆:把表格想成一个花名册。
thead是封面上印的栏目名(”姓名、部门、工号”),tbody是一页页的具体名单。你整理花名册的时候,肯定是”先把栏目名抄下来当表头,再一条条抄名单”——程序抓表也是这个顺序。这里必须提前打一针疫苗(细节等会儿第五节展开):如果你用
find_all("td")抓,表头会整个漏掉——因为表头是th不是td。这是今天最高频的坑,先记住它。三、动手:把一张大表完整搬成 CSV
照旧,先用”自己捏的网页”练手,手熟了再上真网站。
准备:一张像样的大表
新建文件
值班表2.html(直接照抄):2026年10月值班总表
发布日期:2026-10-18
日期 值班人 班次 联系电话 10-19 张三 早班 138****0001 10-20 李四 晚班 138****0002 10-21 王五 早班 138****0003 10-22 赵六 晚班 138****0004 10-23 孙七 早班 138****0005 注意跟上一篇的区别:4 列、表头用
th、数据在tbody里、5 行数据。这就是”真表格”的标准长相。程序:四步搬表
新建
spider2.py:from bs4 import BeautifulSoup import csv # 第1步:拿到源码(先拿本地文件练,一会儿换成真网页) with open("值班表2.html", encoding="utf-8") as f: html = f.read() # 第2步:拆成套娃结构 soup = BeautifulSoup(html, "html.parser") # 第3步:分开抓表头和数据 # 3.1 抓表头:thead 里的所有 th header_cells = soup.find("thead").find_all("th") header = [cell.get_text(strip=True) for cell in header_cells] print("表头是:", header) # 3.2 抓数据:tbody 里的每一行,每行里的所有 td rows = [] for tr in soup.find("tbody").find_all("tr"): cells = tr.find_all("td") row = [cell.get_text(strip=True) for cell in cells] rows.append(row) print(f"共抓到 {len(rows)} 行数据") for row in rows: print(row) # 第4步:存成 CSV with open("值班总表.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(header) # 先写表头 writer.writerows(rows) # 再写数据 print("已保存为 值班总表.csv")运行:
python spider2.py预期输出:
表头是: ['日期', '值班人', '班次', '联系电话'] 共抓到 5 行数据 ['10-19', '张三', '早班', '138****0001'] ['10-20', '李四', '晚班', '138****0002'] ... 已保存为 值班总表.csv打开生成的
值班总表.csv——用 Excel 或 WPS 打开,表头一行、数据五行、列对得齐齐整整。一张大表,完完整整搬回家了。逐行拆给你看
代码不长,但每个零件都值得看一眼:
soup.find("thead").find_all("th"):先找”表头区”,再在里面找所有th。注意这个链式写法——find返回的盒子上还能再find_all,意思是在这个盒子里继续找。这样写的好处是:万一页面里有好几个表格(真网站常有的事),你只在”第一个表格的表头区”里找,不会抓串。get_text(strip=True):比上一篇多了一个strip=True。它的意思是”取文字,顺便把前后的空格、换行都擦掉”。真网页的格子里常有换行和缩进,不加 strip 抓出来的值是" 张三 "这种带空格的,看着一样,比对和筛选的时候全是毛病。for tr in soup.find("tbody").find_all("tr"):一行一行来,每次把一行里的td全取出来变成一个列表,攒进rows。rows就是”列表的列表”——第8篇的字典列表你都玩过了,这个不陌生。csv.writer+writerow/writerows:第25篇学的 CSV 手艺原样复用。writerow写一行(表头),writerows写多行(数据)。注意newline=""不能省,不加它 Windows 上生成的 CSV 会多出空行;utf-8-sig是为了让 Excel 打开中文不乱码——这两个小参数是第25篇讲过的老坑,今天顺手就防了。
换成真网页:还是只改一行
和上一篇一样,把”读文件”换成”发请求”:
import requests resp = requests.get("https://example.com/duty", timeout=10) resp.encoding = resp.apparent_encoding # 防中文乱码,老疫苗 html = resp.text其余一行不动。这就是分层测试的好处:提取逻辑在本地文件上验证过是通的,换真网页只动”取源码”这一步,出错也只可能出在这一步,排查范围缩到最小。
四、跟AI说需求 → 读懂 → 改进
第一步:跟AI说需求
今天的参考需求长这样(注意它比上一篇多了”表头/数据分离”和”输出文件”两个要素):
“我有一个网页,里面有一张表格,结构是:
里一行是表头(4列:日期、值班人、班次、联系电话), 里每行是 4 个的数据。请写 Python 代码:用 BeautifulSoup 分别抓出表头和所有数据行,保存成 CSV 文件(表头在第一行),打印抓到的行数。先用本地文件 值班表2.html 测试,代码里留好注释,标出哪一段是’以后换成真网页要改的地方’。” 要素清单:目标(存CSV)、结构(thead/th + tbody/td、几列几行)、输出(文件名、格式)、容错(留注释、打印行数)。四要素齐了,AI 一次就能写对。上一篇练习3你写的”抓全校课表”需求,今天正好用上——如果当时写得够具体,直接粘给 AI 就行。
第二步:读懂AI给的代码
读懂的标准还是”能回答三个问题”:
- 哪一行抓表头?为什么用
th不用td?(表头住在th里,td只装数据) - 哪一行把”一行”变成”一个列表”?(
row = [cell.get_text(strip=True) for cell in cells]) - CSV 文件里,表头是怎么保证在第一行的?(先
writerow(header)再writerows(rows)——顺序决定一切)
能答上这三个,你就读懂了。答不上?把对应那行单独贴给AI:”这行在干什么?用花名册的比喻解释。”
第三步:改进
读懂之后提新要求。今天推荐这两个:
“①现在如果某行数据缺一个格子(
只有3个),列就错位了。帮我加上检查:每行格子数必须和表头列数一样多,不一样就在屏幕上打印警告,标出是第几行、少了哪个。②帮我加一句统计:打印每个’班次’各有多少人(早班X人、晚班Y人)。” 第①个改进用的是第5篇的条件判断 + 第33篇的计数思路;第②个用的是字典计数(第8篇)或者
collections.Counter。看出来了吧:今天没有新知识,改进用的全是老手艺。这就是系列设计的小心思——每隔几篇来一次”纯组合练习”,把散装手艺拧成一股绳。五、四个必踩的坑(先打疫苗)
坑1:
find_all("td")抓不到表头,或者表头行的格子数对不上。表头是th不是td。如果你不分thead/tbody、直接find_all("tr")再find_all("td"),表头那一行会抓出一个空列表(因为它的格子全是th),然后写进 CSV 变成一行空白,列名全丢。疫苗:抓表一定分两路——表头走th,数据走td。顺带记住:万一遇到”偷懒网页”(表头也用td加粗显示),就用find_all(["th", "td"])两样都要,先看源码确认是哪种,别猜。坑2:没加
strip=True,抓出来的值带一堆空格换行。真网页为了排版好看,源码里格子内部常有换行和缩进,比如n 张三n 。不 strip,抓出来是
"n 张三n",存进 CSV 后肉眼看不出问题,但你一做筛选、比对、去重就全崩了。疫苗:表格提取的get_text一律带strip=True,写成肌肉记忆。坑3:页面上有多个表格,抓错了对象。真网页经常一张页子里塞好几个表(导航表、布局表、数据表……早期网页甚至拿表格排版)。
soup.find("table")抓到的第一个可能根本不是你要的。疫苗:先 F12 在查看器里确认目标表格的id或class(比如class="duty-table"),然后soup.find("table", class_="duty-table")精确定位。定位你来(看源码),写码它来(AI)——上一篇的规矩继续有效。找不到特征?退而求其次:把几个表格都抓下来各自打印前两行,肉眼挑出目标,再回头加定位条件。坑4:合并单元格(rowspan/colspan)让行列错位。公示表里”一等奖 3 人”这种合并格很常见,HTML 里写作
rowspan="3"——一个格子占三行的位置。后果是:接下来两行每行只有 3 个td(而不是 4 个),你按”每行4列”硬拼,数据就错位了。疫苗:今天先学会识别它——如果发现”某些行的格子数和表头列数对不上”,十有八九是合并单元格,别硬写代码硬凑。正确姿势:跟AI说”这个表格有 rowspan/colspan 合并单元格,请帮我写一个能把表格补全成规整行列的函数”,让它帮你展开合并格。这是硬骨头,留给你当练习3的选做题,能啃下来说明你表格提取出师了。六、跟AI协作的正确姿势(抓表格特供版)
- 给AI”表格的源码片段”,而不是整页源码。F12 找到
,右键复制这个元素(Copy element),只把这一段给AI:”表格源码是这样,请写提取代码。”几十行解决战斗,AI 写出来的定位代码也准得多。
- 让AI先写”检查”,再写”提取”。新需求可以这么提:”先写一段代码,打印出这个表格一共有几行、每行几个格子,确认行列整齐之后,再写提取保存的部分。”这叫先勘测后施工,是爬虫的第一美德——行列都不齐就闷头抓,抓回来的是垃圾。
- 让AI解释生成的 CSV 结构:”这个 CSV 是怎么保证表头在第一行的?writerow 和 writerows 有什么区别?”——第25篇的复习点,值得再问一遍。
- 改完先跑本地文件,再跑真网页。老规矩:本地文件跑通 = 逻辑没问题;换真网页崩了 = 是取源码/定位的问题。分层测试,永远省你最多的时间。
七、今日小练习
练习1:跑通大表搬运。照抄
值班表2.html和spider2.py,运行并确认生成的值班总表.csv在 Excel 里打开是”1行表头 + 5行数据、4列对齐”。然后改 HTML:加一行周六的值班数据,重新运行,确认 CSV 变成 6 行数据(不用改一行代码)。提示:这题练的是”程序抓的是文件的当前内容”。如果行数没变,检查你保存的是不是同一个文件、跑的是不是同一个脚本。
练习2:给表格”数个数”。修改
spider2.py,在保存 CSV 之前打印两条统计:一共几列(用表头的长度算)、一共几行数据。再加一条”体检”:遍历每一行,如果某行的格子数不等于列数,打印”第N行格子数不对,有M个格子”。提示:列数 =
len(header),行数 =len(rows);体检就是循环里一句if len(row) != len(header):(第5篇的手艺)。写完手动把 HTML 里某行删掉一个测试,看警告能不能报出来——故意弄坏来测试,是程序员的基本功。 练习3(动脑+选做):把上一篇的”全校课表”需求落地。拿出你练习3写的需求,粘给AI,让它写完整代码。课表比值班表多一个难点:表头是”星期一~星期五”,左边还有一列”第1~6节课”(就是个每行都有的普通格子,注意它也算一列)。目标:生成的 CSV 第一行是”节次/星期一/…/星期五”。
提示:把”每行第一个
是节次”这个信息明确写进需求里。选做挑战:如果 AI 给的代码没处理”某天没课的空格子”( ,get_text 出来是空字符串),问它:”空格子怎么区分’没课’和’漏抓’?”——这是个好问题,能带你触到爬虫的深水区。合并单元格的表格(坑4)如果也想啃,就当本课毕业考。下篇预告
下一篇(第43篇):爬虫进阶 — 一个人干完一个部门的活儿。今天你搬的是一张静态大表,下一篇上真正的强度:一个网站的数据分散在好几页(分页),抓回来的东西有重复(去重),中途网络抽风、网页改版报错(异常处理)。三样手艺一上,你的爬虫就从”能跑”进化到”能干活”。今天先把你”行列整齐、一格不落”的手艺练扎实——下一篇的地基就打在这上面。
评论
发表回复