☰
Python requests库办公自动化实战:批量查询、数据抓取与报表下载
2026/9/26 13:52:19 网站建设 项目流程

你们有没有遇到过这种情况:领导甩过来一张Excel表格,里面躺着几十个订单号,让你挨个去快递官网查物流状态,查完再把结果填回去。手动打开网页、复制单号、点查询、复制结果、粘贴到表格,一个单号折腾两三分钟,五十个就是两个多小时。中间还得全神贯注,漏一行就要回头对一遍。我当时干完这活儿就一直在想,要是能写个脚本自动跑,每天能省出多少时间干正事。

后来帮我解决问题的,就是今天要聊的主角:Python的requests库。它本身是一个HTTP请求库,但在自动化办公这个场景里,它几乎就是那个替你“跑腿”的人——你告诉它去哪个网址要什么数据,它就带着你的参数去访问服务器,再把返回内容交到你手里。配合openpyxl、pandas这些库,网页接口里的数据就能变成你想要的表格、文件、统计结果。

这篇文章不打算从零给你讲HTTP协议原理,而是从我实际干过的活儿出发,拆一拆requests库在办公自动化里最常用的那些用法:环境怎么搭、get/post怎么用、几个能直接改改就用的脚本,还有我踩过的坑。适合刚开始写Python、想用编程替代重复劳动的办公族,也适合运营、数据分析、财务这类经常跟网页数据打交道的岗位。看完你至少能自己写出第一段自动抓数据的脚本。

1. 为什么自动化办公绕不开requests库

1.1 它到底是个什么玩意儿

requests库在Python里做的事情,简单说就是“帮你发HTTP请求”。你平时用浏览器访问网页,本质上是你的浏览器向服务器发了一个GET请求,服务器再把网页内容返回给你;你在网页上提交表单,本质上是浏览器向服务器发了一个POST请求,服务器处理完再给结果。requests库就是把这一整套动作,用几行Python代码复现出来,而且能做到比手工操作更快、更稳定、可批量。

我习惯用一个比喻:把服务器想成一个前台客服,浏览器是打电话的座机,requests库则是一个能自动拨号、自动说话、自动记录的机器人。你写代码告诉它“拨打XX号码,接通后问XXX”,它就会按你的指示一遍一遍执行,永远不会烦、不会累、不会手抖。

在自动化办公的场景里,requests库最常见的用途有这几类:

  • 抓取网页内容:批量获取网站上公开的信息、列表数据。
  • 调用接口:很多系统都提供了JSON格式的数据接口,requests就是用来发请求拿数据最方便的工具。
  • 模拟表单提交:比如登录内部系统、提交审批数据(前提是合规,别碰红线)。
  • 配合文件下载:批量下载报表、附件、图片、压缩包。

所以它更像是自动化办公的“地基”。上面的表格处理、文本处理、邮件发送,都是在有了数据之后的事;requests负责的是数据从哪儿来。

1.2 手工操作的痛点和requests的解题思路

我之前帮一个做电商运营的朋友处理过需求:他们每天早上要登录供应商后台,把那天的发货单号、物流状态、预计到达时间导出来,做成内部看板。最开始全靠人工复制粘贴,一个多小时才能整理完,还经常因为手误漏数据。

用requests之后,整个流程变成了:

  1. 发送登录请求,拿到登录凭证。
  2. 带着凭证去请求发货单列表的接口。
  3. 拿到JSON数据后,用pandas整理成结构化表格。
  4. 自动保存成Excel发到群里。

四步加起来,跑一次不到30秒。这就是requests在办公自动化里的核心价值:把“人工打开网页、复制、粘贴、重复一百遍”变成“写一次脚本,跑一百遍都不用管”。

为什么选requests而不是别的方案?你可能听说过Selenium、Playwright这些模拟浏览器的工具,它们确实也能做这些事,但更重。requests是直接跟服务器通信,不加载JavaScript、不渲染页面,速度可能快上十几倍,资源占用也小得多。如果目标数据在接口里就能拿到,完全没必要动用浏览器自动化这种重武器。requests的定位是轻量、精准、直接。

2. 上手前的三步准备工作

在写第一行requests代码之前,先把环境准备好。这一步没什么技术含量,却是新手最容易卡住的地方。我在公司帮同事装环境的时候,见过各种千奇百怪的问题,大部分都出在这几步上。

2.1 Python环境安装

如果你已经装好了Python,可以跳过这一节;如果还没装,建议直接去Python官网下载安装包。有两个小建议:

  • 版本选择:直接装最新的稳定版就行,别装太老的版本。requests库对版本要求不算苛刻,Python 3.6以上都能跑,但新版本在语法和依赖处理上更省心。
  • 安装时勾选“Add Python to PATH”:这一步很关键。勾了之后,你在命令行里敲python才能直接启动;不勾的话,后续还得手动配环境变量,新手往往在这里卡壳。

装完之后,打开命令行(Windows下是CMD或PowerShell,macOS是终端),敲:

python --version

能看到类似Python 3.12.x的输出,就说明装好了。

2.2 用pip安装requests库

requests库不是Python自带的,需要额外安装。在命令行里执行:

pip install requests

如果速度太慢或者超时,换成国内镜像源:

pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成后验证一下:

pip show requests

能看到版本信息说明装好了。

这里有一个很多人忽略的小问题:如果电脑上同时装了Python 2和Python 3,或者用了Anaconda,pip和python可能指向不同的环境。最简单粗暴的检查方法,是在Python交互环境里执行:

python

进入交互模式后输入:

import requests print(requests.__version__)

如果正常输出版本号,说明requests库装在了当前这个Python环境里。如果报ModuleNotFoundError,说明pip装的跟当前Python不是同一个环境,要么改用python -m pip install requests,要么检查一下PATH配置。

2.3 第一段测试代码

环境就绪后,先跑一段最简单的代码,验证一切正常。拿一个稳定的接口练手:

import requests url = "https://api.github.com" resp = requests.get(url, timeout=5) print(resp.status_code)

如果输出200,说明你已经成功发了一个HTTPS请求并拿到了响应。200在HTTP协议里的含义是“请求成功”。这一步跑通之后,你就等于在门口探了半个身子进去了。

3. 核心API拆解:get方法和它的伙伴们

requests库的API设计得非常简洁,一共就那么几个方法,但每个方法背后的细节都值得琢磨。这一节我把最常用的参数和用法拆开讲清楚,每个参数都会说明在自动化办公里什么时候会用到。

3.1 get方法:最常用的请求方式

get方法是你接触requests库第一眼就会遇到的方法,完整写法是:

resp = requests.get( url, # 请求的网址 params=None, # 查询参数,字典形式 headers=None, # 请求头 timeout=None, # 超时时间 verify=True, # 是否验证SSL证书 )

先说url,这是必传的。然后是params,这是我在办公自动化里用得最多的参数之一。举一个实际例子:假设系统后端有一个订单查询接口,地址是https://api.example.com/orders,它要求按分页取数据,每页20条:

import requests page = 1 params = { "page": page, "page_size": 20, "status": "shipped", } resp = requests.get("https://api.example.com/orders", params=params, timeout=10) print(resp.url) # 打印实际请求的完整URL

代码运行之后,resp.url会变成类似https://api.example.com/orders?page=1&page_size=20&status=shipped的样子。requests库会自动把params字典拼到URL的查询字符串里,不需要你手动拼。这个设计看着不起眼,用起来是真省心——不少新人喜欢手动拼URL字符串,一旦参数里含有中文、空格、&号这类特殊字符,不处理就会出错,而用params参数传,requests会帮你做URL编码。

headers也不容忽视。很多服务器会校验请求头里的User-Agent,用来判断访问者是不是真人浏览器。如果你的请求头显示是Python代码,部分网站会直接拒绝,或者返回验证码。最省事的做法是设置一个常见的浏览器用户代理:

headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", "Accept": "application/json, text/plain, */*", } resp = requests.get(url, headers=headers, timeout=10)

这里插一句,设置浏览器身份这件事在官方文档里叫“设置Headers”。我的态度是:用于自动化办公场景、抓取公开数据、频率控制得当,问题不大;但如果你对着一家网站的接口没完没了地高频请求,那不管怎么伪装都绕不过封IP。

timeout这个参数,强烈建议每次都写。不写timeout的话,一旦目标服务器不响应,脚本会一直干等,可能等几分钟甚至更久才报错。设一个合理的超时时间,比如10秒,告诉requests“10秒没响应就别等了直接报错”,脚本才不会卡死在一个坏请求上。我写过不少自动化脚本,刚开始偷懒不写timeout,结果脚本在半夜定时任务里卡了一整晚,第二天到公司一看,毛都没跑完,从那以后每个请求我都老老实实带上timeout。

verify是SSL证书验证开关,常规场景保持默认True就行。但某些内部系统用的是自签名证书,请求时会报SSLError,这种情况只能在明确知道来源、合规的前提下改成verify=False,不推荐随便用。

3.2 post方法:模拟提交和数据上传

get方法适合“获取”数据,但很多办公场景需要“提交”数据:登录系统、提交表单、上传数据。这时候用的是post方法。

post方法最核心的区别在于数据的传递方式。get把参数放在URL里,post把数据放在请求体里。requests的post方法支持两种常见的数据格式:

import requests # 方式一:表单格式 payload = { "username": "admin", "password": "123456", } resp = requests.post("https://api.example.com/login", data=payload, timeout=10) # 方式二:JSON格式 payload = { "username": "admin", "password": "123456", } resp = requests.post("https://api.example.com/login", json=payload, timeout=10)

data=传的是表单格式,requests会帮你编码成username=admin&password=123456这种样子,发送时请求头里的Content-Type是application/x-www-form-urlencoded;json=传的是JSON字符串,requests会帮你自动序列化,Content-Type是application/json。

在自动化办公里怎么选?看对方接口的要求。如果是老一点的系统、或者网页上那种传统表单,一般用data=;如果是新一些的RESTful风格接口,通常用json=。判断方法也很简单:先在浏览器里打开你要模拟的页面,按F12切到Network面板,提交一次表单,看请求头里的Content-Type是什么,照着填就行。这个技巧属于“看一遍就会”级别的实用技能。

我见过不少人在这一步栽跟头:明明接口要求JSON格式,代码却用了data=,结果服务器一直返回400 Bad Request,翻接口文档半天才发现是格式不对。反过来也一样。记住一个原则:接口要求什么格式,你就用什么参数传。

3.3 响应对象:拿到数据之后怎么读

发请求只是前半场,拿到数据之后的后半场同样关键。requests库发送请求后返回的是一个Response对象,有几个属性需要吃透:

  1. resp.status_code:HTTP状态码。200是成功,404是找不到资源,403是禁止访问,500是服务器错误。写脚本时可以加个判断,状态码不是200就记日志。
  2. resp.text:服务器返回的内容,以文本形式呈现。
  3. resp.json():如果服务器返回的是JSON格式数据,用这个方法直接解析成Python字典或列表。
  4. resp.content:服务器返回的原始字节流,下载图片、文件的时候用这个。
  5. resp.encoding:文本编码。上面的text是requests根据响应头推断的编码,如果推断不对就会出现乱码,需要手动指定。

有一个办公场景非常典型:某个内部系统返回的JSON数据里,中文本来应该是正常的,但用resp.text打印出来全是乱码。原因通常不是数据坏了,而是编码推断错了。这种时候可以手动指定编码:

resp = requests.get(url, timeout=10) resp.encoding = "utf-8" print(resp.text)

如果还是乱码,可能是对方返回的编码是gbk,那就试试:

resp.encoding = "gbk"

在自动化办公里,绝大多数编码问题都能用这两行解决。编码问题后面我还会单独展开讲,这里先留个印象。

4. 三个能直接抄作业的自动化办公实战

光讲API不带实战,等于没讲。这一节我放三个自己用过的办公自动化脚本,每个都能直接改改参数就拿去用。这三个案例分别对应requests库在办公场景里最常见的三种需求类型:批量抓取网页数据、自动下载报表文件、批量查询并写回Excel。

4.1 批量抓取网页公开数据

第一个实战场景:老板让你把某网站上某个分类下的商品列表全部抓下来,整理成Excel。网站的信息是公开的,不需要登录,数据通过接口返回JSON格式。这种场景最适合requests库上手。

先分析接口结构。假设接口地址是https://api.example.com/products,参数里有page和size,返回结果里的data字段是一个列表,每一项包含商品名称、价格、销量。代码可以这样写:

import requests import json import time import pandas as pd headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", } all_data = [] for page in range(1, 11): params = { "page": page, "size": 50, } resp = requests.get( "https://api.example.com/products", params=params, headers=headers, timeout=10, ) if resp.status_code == 200: data = resp.json() items = data.get("data", []) all_data.extend(items) print(f"第{page}页,获取{len(items)}条") else: print(f"第{page}页请求失败,状态码{resp.status_code}") time.sleep(1) # 温和请求,别一下把所有请求打出去 # 保存到Excel df = pd.DataFrame(all_data) df.to_excel("products.xlsx", index=False)

这段代码里有一个关键点:time.sleep(1)。很多人写脚本根本不在乎请求频率,几十个请求一口气打出去,速度快到服务器来不及反应,结果就是IP被限制。加个sleep做节流,每个请求间隔一秒,数据抓完也就多花几十秒,但被封的风险大幅降低。我见过太多人为了快那几秒,把账号或者IP搞进黑名单,得不偿失。

还有一个细节是分页。有些接口的页数上限不是固定的,可能总共只有5页,硬翻到第10页会返回空列表。更稳妥的做法是循环到“返回数据为空”就终止,而不是硬编码10页:

page = 1 while True: params = {"page": page, "size": 50} resp = requests.get(url, params=params, headers=headers, timeout=10) data = resp.json().get("data", []) if not data: break all_data.extend(data) page += 1 time.sleep(1)

这种写法更贴近真实工作场景——你不知道对方有多少页数据,那就让程序自己判断。

4.2 自动下载报表文件

第二个场景:每天需要从管理后台下载一份CSV格式的销售报表。下载链接通常长这样:https://api.example.com/reports/daily.csv?date=2025-03-18,需要登录后的Cookie才能访问。手工操作是每天打开浏览器、登录、点下载。用requests可以把整个流程压成几行脚本。

首先需要拿到登录凭证。如果系统是基于Cookie的,最直接的方式是用requests.Session保持会话,先登录一次,之后的请求都自动带上Cookie:

import requests session = requests.Session() # 第一步:登录 login_data = { "username": "your_account", "password": "your_password", } login_resp = session.post( "https://api.example.com/login", data=login_data, timeout=10, ) print("登录状态:", login_resp.status_code) # 第二步:带着会话下载报表 report_url = "https://api.example.com/reports/daily.csv" params = {"date": "2025-03-18"} resp = session.get(report_url, params=params, timeout=15) # 第三步:保存文件 if resp.status_code == 200: with open("daily_report.csv", "wb") as f: f.write(resp.content) print("报表下载完成") else: print("下载失败,状态码", resp.status_code)

这里用session.get而不是requests.get,关键就在于Session对象会自动管理Cookie。如果改用普通的requests.get,第一次登录拿到的Cookie不会被保存,第二次请求下载报表时,服务器根本不认你。这个坑我见过很多人踩过——第一段代码跑通了登录,第二段代码下载报表却拿不到数据,就是因为没用Session。

下载文件的时候,注意用resp.content而不是resp.text。文件可能是二进制内容,用text会经历编码解码的过程,轻则效率低,重则文件损坏。resp.content拿到的是原始字节流,open以wb(二进制写入)模式保存,文件才能跟浏览器下载下来的完全一致。

4.3 批量查询并自动写Excel

第三个场景是一个综合案例:批量查询几十个订单的物流状态,把结果整理成Excel发邮件给同事。这就是文章开头说的那个场景。

假设物流查询接口是https://api.example.com/logistics/query,接收一个tracking_no参数,返回物流轨迹。代码可以这样写:

import requests import pandas as pd import time order_list = ["SF123456789", "SF987654321", "YT1234567890"] headers = { "User-Agent": "Mozilla/5.0 ...", } results = [] for tracking_no in order_list: params = {"tracking_no": tracking_no} try: resp = requests.get( "https://api.example.com/logistics/query", params=params, headers=headers, timeout=8, ) if resp.status_code == 200: data = resp.json() latest_status = data["data"]["latest_status"] results.append({ "单号": tracking_no, "状态": latest_status, "查询时间": pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S"), }) print(f"{tracking_no} 查询成功:{latest_status}") else: results.append({ "单号": tracking_no, "状态": f"请求失败{resp.status_code}", }) except requests.exceptions.Timeout: results.append({ "单号": tracking_no, "状态": "请求超时", }) except requests.exceptions.RequestException as e: results.append({ "单号": tracking_no, "状态": f"异常:{e}", }) time.sleep(1) df = pd.DataFrame(results) df.to_excel("物流状态.xlsx", index=False) print("全部查询完成,结果已保存")

这段代码里我加了异常处理。为什么要加?因为网络世界没有百分之百的可靠,任何一环都可能出错:超时、断网、目标服务器临时故障。如果代码里不做异常处理,只要一个订单查询失败,整个脚本就抛异常中断,后面的单号全部白等。加上try...except之后,单个订单失败只会记录一条失败状态,后面照常执行,这才是自动化脚本该有的健壮性。

关于频率控制多说一句:这里每个订单之间sleep 1秒,50个订单就是50秒的额外等待,但换来的是接口稳定不被封。如果是公司内部接口、频率限制比较宽,可以适当缩短间隔;如果是外部公共接口,宁慢勿快。

5. 我踩过的坑和排查清单

理论讲完、案例也给完了,最后分享一些实际使用中反复遇到的问题。每一件都是我或者身边同事真实踩过的,拿出来晒晒,帮大家少走点弯路。

5.1 编码乱码,全乱套了

乱码问题是requests新手遇到的头号麻烦。现象是接口明明返回了中文,打印出来却是–这类鬼画符。原因前面提过:requests会从响应头的Content-Type里找编码信息,如果服务器没设置或者设置得不对,requests就猜。猜错了就乱码。

我的排查顺序是这样的:

  1. 先看响应头里写了什么编码:
resp = requests.get(url, timeout=10) print(resp.headers.get("Content-Type"))
  1. 如果看到charset=utf-8但还乱码,那可能是resp.text用错了,直接用resp.content.decode("utf-8")解码。

  2. 如果响应头里没写charset,依次试utf-8、gbk、gb2312、latin1。对中文站点,百分之九十的可能落在gbk或utf-8。

一个小技巧:很多老系统返回的是GBK编码,但响应头没声明,requests会默认按ISO-8859-1解码,必然乱。遇到这种情况,最优解是每次都手动指定resp.encoding = "gbk",或者用resp.content.decode("gbk")。

5.2 请求超时和连接失败

办公自动化脚本最怕的就是“今天跑不通了”,而超时是第二大常见原因。表现是脚本抛requests.exceptions.ConnectTimeout或者requests.exceptions.ConnectionError。

排查思路:

  • 先看目标地址在浏览器里能不能正常打开。如果浏览器都打不开,那代码再怎么写也没用。
  • 看是不是网络代理问题。公司内网环境往往设有代理,requests默认不读系统代理设置。如果公司网络需要代理才能访问外部站点,需要显式指定代理:
proxies = { "http": "http://proxy.example.com:8080", "https": "http://proxy.example.com:8080", } resp = requests.get(url, proxies=proxies, timeout=10)

这里需要提醒一下:如果你本机开了其他网络软件,也可能导致requests请求异常。requests在trust_env为真的情况下会读取环境变量里的代理设置。如果开发环境里代理配置比较混乱,requests也会一并“吃”进去,结果就是本地访问正常、代码跑不了。排查手段很简单,先关掉本机的网络代理工具再试一次代码能不能跑通。

5.3 反爬与频率控制

很多网站对自动化访问是有防护的:识别UA、检测请求频率、限制IP访问次数。办公自动化场景下,一定要记住一个原则:拿数据可以,但别把人服务器搞崩。

我的经验是:

  • 单次任务请求量控制在100以下,大型任务控制在1000以下。
  • 请求间隔至少0.5到1秒,大任务之间留出缓冲。
  • 如果对方返回验证码、403、503,立刻停手,检查是不是访问太频繁。
  • 优先找官方API,别死磕网页抓取。

我印象比较深的一次:为了赶一个数据分析任务,凌晨写的脚本没做任何节流,几百个请求一口气全发出去,结果触发了对端的安全策略,导致后续每个请求都被拒。那次之后,我的脚本里永远有sleep,并配合重试逻辑——请求失败最多重试3次,每次都等更久,依然失败就记录在日志里等下次再跑。

5.4 常见问题速查表

最后整理一份排查清单,遇到问题可以直接对号入座:

常见问题可能原因快速解法
中文乱码编码推断错误手动指定resp.encoding = "utf-8"或"gbk";或用resp.content.decode()
请求超时超时时间太短 / 网络不通增大timeout;检查网络与代理配置
403 Forbidden缺少请求头 / UA被识别加上合法的浏览器User-Agent头;降低请求频率
状态码200但数据为空接口参数缺失或错误在浏览器里打开请求地址比对参数;检查params拼写
JSON解析报错返回内容不是标准JSON先print(resp.text)看原始内容;可能是登录失效返回了HTML
登录后请求仍未授权会话未保持改用requests.Session(),先登录再请求

5.5 几条提升脚本稳定性的建议

最后这部分,分享几个让自动化脚本更抗造的做法,纯经验之谈。

第一,永远给每个请求加timeout。哪怕只写一个timeout=3,也强过不写。不写timeout的脚本,遇到网络异常就像在等一辆永远不会来的车。我自己的习惯是GET请求8秒,POST请求10秒,下载文件15秒。

第二,写重试逻辑。网络请求失败不一定致命,很多只是偶发。实现一个简单的重试:

import time def fetch_with_retry(url, headers, params=None, retries=3): for attempt in range(retries): try: resp = requests.get(url, params=params, headers=headers, timeout=8) if resp.status_code == 200: return resp print(f"第{attempt + 1}次尝试,状态码{resp.status_code}") except requests.exceptions.RequestException as e: print(f"第{attempt + 1}次尝试失败:{e}") time.sleep(2 * (attempt + 1)) return None

这个函数会在失败之后等2秒、4秒、6秒再重试,三次都失败就返回None。把这段写进所有脚本里,相当于给脚本加了一道保险。

第三,日志输出不能省。自动化脚本跑失败的时候,你需要知道它卡在哪一步。我习惯在关键节点打印时间戳和状态码,比如:

print(f"[{datetime.now()}] 正在处理第{page}页...")

这样就算脚本半夜跑挂了,第二天一眼看日志,立刻知道是哪一步出了问题。脚本跑得久不久不重要,跑得明明白白才重要。

requests库本身确实简单,难的是你愿不愿意把那些重复劳动写进代码里。我第一次写自动化脚本的时候,光是查API文档就花了半天,但那个脚本后来用了整整一年,每天帮我省下半小时。所以我的建议很简单——下次再遇到让你反复点鼠标的活儿,先别急着动手,想想能不能用requests写几行代码,然后让电脑去跑,你去泡杯茶。这就是自动化办公最朴素的乐趣。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询