上一篇的爬虫再能干,也只是个”只读”的观众:网页给什么,它看什么。但生活里的查询网站都是”问答式”的——你得先在表单里填月份、输关键词、点”查询”,它才把结果吐给你。今天教程序开口说话:填表、提交、拿结果。新概念只有一个:
requests.post。学完这篇,你的程序就从”看热闹的”升级成”能替你去窗口排队办事的”。
一、真实场景:每月一次的”填表查询”折磨
你在单位兼着一个活儿:每月月初,去人社局的”补贴发放查询”页面,把当月的公示结果逐条查出来、整理成表。查询页面长这样:一个下拉框选月份(2026-09、2026-10……),一个输入框填姓名或单位关键词,一个”查询”按钮。点一下,页面跳到结果页,列出匹配的记录。
问题是你手里有20个单位名要逐个查。于是每月这天你的操作是:选月份→粘贴”沈阳第一机床厂”→点查询→看结果→抄下来→回来→粘贴”沈阳水泵厂”→点查询→抄下来……20个单位查20次,抄错一个字就得重来。查完抬头一小时过去了,而这活儿每月一次,年年如此。
上周的爬虫对这个页面完全没辙:它只能”看”现成的网页,而这个页面你不点查询按钮,它就永远是空空的表单。差别在哪?上周的程序只会”问路”(打开网址),今天的程序要学会”递表单”(提交条件)。
今天全部的内容就是这一件事:让程序替你填表、点查询、把结果收回来。新概念只有一句话:
requests.post(网址, data=表单内容)= 让程序代替你,把填好的表单递进窗口。
就这一句。剩下的全是老手艺:循环(第6篇)批量查20个单位、BeautifulSoup(第41篇)从结果页抠数据、csv(第25篇)存结果、try/except(第10篇)防崩。
二、新概念只有一个:GET 是明信片,POST 是信封
先花两分钟把”提交表单”的原理看懂,写代码时就不慌了。
你早就用过 requests.get(网址)(第37篇)——那是把网址发出去,把页面拿回来。它像寄明信片:你要什么全写在明信片(网址)上,邮递员(服务器)看得见,路人也看得见。
而填表单提交是 requests.post(网址, data=表单内容)——把表单内容塞进信封里寄出去。服务器拆开信封、看完你的条件、把结果寄回来。这就是今天的新朋友:
import requests
# 假设查询页面的网址是 https://example.gov.cn/query
resp = requests.post(
"https://example.gov.cn/query",
data={"month": "2026-10", "keyword": "沈阳第一机床厂"}
)
print(resp.status_code) # 200 = 服务器收到并处理了
print(resp.text[:500]) # 返回的结果页面(HTML)
三个细节,一个都不能少:
data=是个字典,一个”表单字段名:你填的值”。注意是字段名(网页里的那个month),不是你肉眼看到的”月份”两个字。字段名对不上,服务器当没收到——不报错、不吭声、静默返回空结果,这是今天头号大坑,疫苗在第五节。- POST 和 GET 是两种”递表单”的方式。查询类表单用 GET 还是 POST,是网站定的,你不用纠结——F12 一看便知(马上教你)。今天学 POST,因为它更能代表”提交表单”这件事。
resp还是那个resp。不管 GET 还是 POST,拿回来的东西一模一样:resp.text是 HTML、resp.status_code是状态码、resp.json()(第38篇)是 JSON。你会的那些解析手艺,全部照用。
类比记忆:
requests.get是在图书馆检索机上输书名——输完回车就出结果;requests.post是到服务台填借书单——你把要什么、条件是什么写在单子上递进去,工作人员处理完把书(结果)给你。上一篇的爬虫是站在书架前抄书名,今天是学会填借书单。
怎么知道一个网页的表单”字段名”是什么、用 GET 还是 POST?F12 侦察三步,一分钟学会:
- 打开查询页面,按 F12,切到 Network(网络) 面板;
- 在页面上随便填一次查询、点”查询”按钮;
- Network 里会出现一条新记录,点它:General 里的 Request Method 写着 POST 还是 GET;往下翻到 Form Data(表单数据),里面就是所有字段名和你填的值——照抄进
data={}就行。
这个侦察动作是今天最值钱的技能。以后遇到任何”填了才能看”的页面,都是这三步。
三、动手:先往”回声墙”扔个纸团
照旧先在安全的地方练手。但 POST 有个麻烦:它没法对着本地 HTML 文件练(file:// 只能”看”,没有”窗口”可递表单)。所以今天换一种练法——两步走:先用一个公开的测试网站确认你的 POST 姿势正确,再自己捏一个”查询网站”实战。
第一步:httpbin.org —— 会回声的墙
https://httpbin.org/post 是个专门给程序员练手的网站:你 POST 什么给它,它就把你发的东西原样回显在返回的 JSON 里。相当于对着山谷扔纸团,山谷把纸团原样扔回来——先确认你的纸团(表单)包对了,再去砸真窗户。
import requests
resp = requests.post(
"https://httpbin.org/post",
data={"month": "2026-10", "keyword": "沈阳第一机床厂"}
)
result = resp.json()
print("服务器收到的表单:", result["form"])
运行后你会看到:
服务器收到的表单: {'month': '2026-10', 'keyword': '沈阳第一机床厂'}
result["form"] 里原样回显了你递的表单——字段名、值,一字不差。看到了这两个字段,说明你的 POST 姿势是标准的。如果这里少了一个字段,就是字段名写错了,还没碰真网站就已经暴露了。
30 秒自查:data= 里的键是字段名(month),值是你填的内容(2026-10);写反了回声墙会立刻告诉你。
第二步:自己捏一个”补贴查询网站”
老规矩,用自己捏的网站练手(跑通了再换真网站,一次只改一个变量——第38篇的规矩)。这次捏的不是静态网页,而是一个会收表单、会回结果的迷你网站,20 行 Python 就能当”站长”。
建一个文件 fake_site.py(这是假网站本体,照抄即可,能看懂更好):
# fake_site.py —— 自己捏的"补贴查询网站",运行后就是一个真网站
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qs
# 假网站的"数据库":月份 -> 多条(姓名, 单位, 金额)
DATA = {
"2026-09": [
("张三", "沈阳第一机床厂", 800),
("李四", "沈阳水泵厂", 1200),
("王五", "沈阳第一机床厂", 600),
],
"2026-10": [
("赵六", "沈阳水泵厂", 900),
("张三", "沈阳第一机床厂", 850),
("孙七", "沈阳变压器厂", 700),
],
}
FORM_PAGE = """
补贴发放查询
补贴发放查询
月份:
2026-09
2026-10
单位关键词:
"""
class Handler(BaseHTTPRequestHandler):
def do_GET(self): # 打开网址:给用户看查询表单
self.send_response(200)
self.send_header("Content-type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(FORM_PAGE.encode())
def do_POST(self): # 收到表单:查"数据库",回一张结果页
length = int(self.headers.get("Content-Length", 0))
params = parse_qs(self.rfile.read(length).decode())
month = params.get("month", [""])[0]
keyword = params.get("keyword", [""])[0]
rows = [r for r in DATA.get(month, []) if keyword in r[1]]
trs = "".join(f"{n} {u} {m} " for n, u, m in rows)
page = f"""
查询结果
查询结果:{month} / {keyword}
姓名 单位 金额
{trs}
"""
self.send_response(200)
self.send_header("Content-type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(page.encode())
def log_message(self, *args): # 别刷屏
pass
print("假网站开张了:http://127.0.0.1:8000")
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
打开一个终端运行它:python fake_site.py,然后浏览器打开 http://127.0.0.1:8000——你会看到一个真的查询页面,能选月份、输关键词、点查询、看结果。恭喜,你现在是站长了。
注意表单里的两行,它们就是 F12 侦察要找的东西: 告诉你字段名是 month, 告诉你字段名是 keyword,method="post" 告诉你用 POST。你捏的网站,字段名当然是你自己起的——但侦察姿势和真网站一模一样。
第三步:写程序去”递表单”
新开一个终端(假网站那个终端要留着跑),建 form_spider.py:
# form_spider.py —— 像人一样填表查询,但一次查完所有单位
import requests
from bs4 import BeautifulSoup
# ---- 配置区 ----
URL = "http://127.0.0.1:8000/query" # 真网站上换成 F12 里看到的提交地址
MONTH = "2026-10"
KEYWORDS = ["沈阳第一机床厂", "沈阳水泵厂", "沈阳变压器厂"]
# ---- 主流程:循环填表、提交、收结果 ----
all_rows = []
for kw in KEYWORDS:
print(f"正在查询:{MONTH} / {kw}")
resp = requests.post(URL, data={"month": MONTH, "keyword": kw})
if resp.status_code != 200:
print(f" 查询失败,状态码:{resp.status_code}")
continue
soup = BeautifulSoup(resp.text, "html.parser")
table = soup.find("table", class_="result")
rows = table.find_all("tr")[1:] if table else []
for tr in rows:
cells = [td.get_text(strip=True) for td in tr.find_all("td")]
all_rows.append([kw] + cells)
print(f" 抓到 {len(rows)} 条")
# ---- 保存 ----
import csv
with open("查询结果.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["查询关键词", "姓名", "单位", "金额"])
writer.writerows(all_rows)
print(f"完成!共 {len(all_rows)} 条,已存入 查询结果.csv")
运行 python form_spider.py,你会看到:
正在查询:2026-10 / 沈阳第一机床厂
抓到 1 条
正在查询:2026-10 / 沈阳水泵厂
抓到 1 条
正在查询:2026-10 / 沈阳变压器厂
抓到 1 条
完成!共 3 条,已存入 查询结果.csv
你手动要干 20 分钟的活儿(查 20 次就是 20 遍同样的动作),程序 3 秒干完,而且一个字都不会抄错。CSV 打开一看,每行还自动带上了”是哪个关键词查出来的”,方便核对。
逐块读懂(30 秒自查):data={"month": ..., "keyword": kw} 就是那张借书单,每个关键词填一次、递一次;soup.find("table", class_="result") 是老朋友 BeautifulSoup 从结果页抠表格(第42篇);continue 是”这次查询失败别停,接着查下一个”(第43篇的容错姿势)。
跟AI说需求的参考说法:
“这是一个查询表单页面的 HTML(贴 FORM_PAGE),我用 requests 提交。帮我写一个脚本:对每个月份×每个关键词提交查询,把结果页里的表格存成 CSV,每次查询失败不中断。”
四、改进:把”查询脚本”变成”每月照跑的工具”
三步升级,都跟AI协作完成:
- 关键词从文件读:把 20 个单位名存成
单位名单.txt一行一个,程序读文件逐个查——下个月名单变了,改 txt 就行,不碰代码。跟AI说:”关键词从 txt 读,一行一个,跳过空行。” - 结果自动带上时间戳:输出文件名带月份,如
查询结果_2026-10.csv,每月跑完留档,不会覆盖上个月的。跟AI说:”输出文件名拼上月份,比如 查询结果_2026-10.csv。” - 跑完自动汇报:末尾打印”查询 20 个关键词、成功 19、失败 1、共 87 条记录”,失败的关键词列出来。汇报是脚本和工具的分界线——工具自己会交代干得怎么样,脚本只顾闷头跑。跟AI说:”最后打印汇总:每个关键词查到几条、有没有失败的。”
五、三个必踩的坑(先打疫苗)
坑1:字段名对不上,服务器”静默”当没收到。data={"月份": "2026-10"} 这种用肉眼看到的中文当字段名的写法,不会报任何错——服务器默默忽略它,返回一个空结果页,你对着空 CSV 猜一天。疫苗:字段名以 F12 的 Form Data 为准(或自己网页源码里 name="xxx" 的 xxx),大小写一个字母都不能差。排查第一招:先把 resp.text 打印出来看服务器到底回了啥——空结果页和报错页长得不一样。
坑2:漏掉隐藏字段,查询怎么都”不对劲”。很多真表单里藏着你看不见的字段: 之类(防止重复提交的令牌、页面标识)。你在网页上看不到它,但提交时它跟着一起递进去了——你的程序漏了它,服务器要么拒绝、要么给默认结果。疫苗:F12 的 Form Data 里有几个字段,data={} 里就写几个,一个不落照抄(包括那些看不懂的乱码值)。这正是 httpbin 回声墙的价值——发之前先对一遍。
坑3:对着真网站乱”递表单”,你不是在测试,你是在添乱。GET 请求只是”看”,POST 提交是有副作用的:报名、投票、注册、下单,递一次就真生效一次。拿真表单练手写循环?一口气提交 20 次报名,主办方的电话就打过来了。疫苗:练习一律在假网站和 httpbin 上做;对真网站只做”查询类”表单(查公示、查成绩、查余额),且单次查询之间隔 1~2 秒(time.sleep(1),第47篇的爬虫礼仪会细讲)。一句话:查询可以自动化,办事要留给人。
六、跟AI协作的正确姿势(递表单特供版)
- 把 Form Data 整个贴给AI:”这是我要提交的表单,F12 抓到的 Form Data 如下(贴原文),帮我写 requests.post 的代码。”字段名一个都不会错——AI 替你抄表单,比你自己抄得还准。
- 先写”回声版”再写”实战版”:”先用 httpbin.org/post 写个 10 行的脚本,确认表单发对了;再改成打真实查询页。”和第43篇”先勘测后施工”同一个道理。
- 让AI帮你读结果页:”这是我提交查询后返回的 HTML(贴 resp.text),里面结果表格的结构是什么?怎么用 BeautifulSoup 把每行的姓名、单位、金额抠出来?”——贴真实返回内容,别让AI猜页面结构。
- 问清楚 GET 和 POST 的判断:”这个表单用的是 POST,如果我遇到用 GET 的查询表单,代码要怎么改?”——答案是:GET 把参数拼在网址后面(
requests.get(url, params={...})),params=和data=用法几乎一样。一问就通,以后两种表单都不怕。
七、今日小练习
练习1:跑通”回声墙”和假网站。先用 httpbin.org/post 确认打印出你发的两个字段;再运行 fake_site.py,浏览器手动查一次”2026-09 / 沈阳第一机床厂”确认网站好使;最后跑 form_spider.py,确认 CSV 里有 3 行且金额没错。
提示:如果 httpbin 那步少了一个字段,九成是 data={} 的键写错了;如果假网站打不开,检查 fake_site.py 的终端是不是还在运行(它得一直跑着,像真的服务器一样)。
练习2:故意填错,看它”安静地死”。把 form_spider.py 里的 "month" 临时改成 "月份",运行。注意观察:程序不报错、不崩溃,只是每条查询都抓到 0 条——这就是坑1说的”静默失败”。然后照疫苗办:打印 resp.text 看服务器回了什么,再把字段名改回来。
提示:能让你看见的失败是好失败,安静的失败才可怕。养成”结果为 0 时先打印原始返回”的本能,这比多学十个语法都值钱。测完记得改回来。
练习3(动脑+选做):做成”每月照跑”的工具。按第四节的三个改进跟AI协作实现:关键词从 txt 读、文件名带月份、末尾汇总汇报。验收标准:把 单位名单.txt 里故意写一个不存在的单位”沈阳不存在厂”,运行后汇报里应该明确写着”该关键词查到 0 条”或列入失败清单,而不是假装一切正常。选做挑战:给假网站加一个”金额下限”表单字段(min_money),程序提交时只查 800 元以上的记录——加一个字段,改两处代码(假网站收参数、爬虫发参数),体会”表单加字段”对两边各意味着什么。
下篇预告
下一篇(第45篇):登录和 Cookie — 让程序带着”工牌”进门。今天你递的表单还都是”匿名查询”,但很多网站得先登录才给查——账号密码递进去,服务器发你一张”工牌”(Cookie),之后每次请求都得带着它。下一篇学 requests.Session:登录一次、带着工牌反复进出,把需要登录的查询也自动化。今天先把”填表、提交、收结果”练成本能——下一篇只是多递一张表(登录表单),外加把工牌揣好。
发表回复