ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python爬虫实战:公开JSON接口批量采集到CSV/Excel全流程

Python爬虫实战:公开JSON接口批量采集到CSV/Excel全流程 先说结论本文不涉及破解付费视频、不提供绕过会员机制的方法也不教任何人去抓取平台内需要授权才能观看的内容。爬虫本身是中性的技术能不能合法使用取决于目标数据是否公开、你是否取得授权、抓取频率是否合理、拿到数据后是否合规使用。下面这套案例目标是本地搭建的公开接口从环境准备、源码编写、批量抓取到数据导出完整跑一遍。只要你把目标换成一个你有权访问、且 robots 和接口规则允许的站点这套流程可以直接复用。这篇文章包含requests 请求封装、分页批量抓取、失败重试、断点续爬、CSV/Excel 导出、爬虫功能封装成 HTTP 接口以及我在评论区经常看到的“PyCharm 爬虫显示 Process finished with exit code 0 却不输出”这类问题的排查思路。1. 本次爬虫案例能干什么这是一套适合入门到进阶过渡的 Python 爬虫项目用一个简易电影列表接口作为演示源覆盖爬虫开发里最常见的工作环节能力项说明演示目标本地 Flask 公开接口不请求任何真实视频站点核心功能分页请求、JSON 解析、数据清洗、CSV 增量写入进阶功能自动重试、请求间隔控制、断点续爬数据输出CSV、Excel也可封装成 HTTP 接口供业务调用主要依赖Python 3.9、requests、Flask、pandas运行平台Windows / Linux / macOS 均可开发工具VS Code、PyCharm、命令行均可这个项目适合以下几类读者刚学完 Python 基础想找一个可运行的爬虫案例练手的人准备把爬虫技能用到数据采集、数据分析、自动化测试场景的人以及想理解请求头、超时、重试、批量抓取这些工程细节而不是只会复制粘贴单页代码的人。不建议把它用在爬取需要登录才能查看的会员内容、破解访问控制、抓取视频流并二次分发等场景。这类行为既容易违反平台用户协议也可能涉及版权侵权和破坏计算机信息系统相关法律风险。爬虫练手目标站点选择和频率控制比写出“能跑”的代码更重要。2. Python 爬虫项目基础架构与适用边界一个规范的 Python 爬虫不应该只是几十行脚本堆在一起。建议至少拆成几层请求层负责发起 HTTP 请求设置请求头、超时、重试。解析层把 HTML 或 JSON 响应解析成结构化数据。清洗层处理空值、去空格、类型转换、字段归一。存储层写入 CSV、Excel、数据库或发到消息队列。调度层控制页码、频率、失败后的重试和续爬。上面的分层看着简单实际写的时候很多人会忽略。最常见的问题是请求和解析写在一个函数里、异常全部吞掉、没有日志、爬一半挂了不知道从哪继续。本文的程序会把这些问题逐个处理掉。合法爬虫的边界也很明确只抓公开可访问、无需绕过访问控制的数据。遵守目标站点的 robots.txt、条款和使用政策。控制请求频率不把目标服务器打到过载。不采集个人隐私信息、不批量下载受版权保护的内容。抓取后的数据用于学习、研究或已获授权的商业用途。如果你的目标是某视频网站的用户行为数据、付费影视元数据、需登录会员接口请先获得平台方明确授权。没有授权的情况下即使技术上能拿到也不建议碰。3. 环境准备与前置条件3.1 Python 环境检查项目基于 Python 3推荐 3.9 以上版本。先确认本机已经安装 Pythonpython --versionWindows 用户如果提示不是内部或外部命令去 Python 官网安装安装时记得勾选 Add Python to PATH。再确认 pip 可用python -m pip --version3.2 安装依赖创建项目目录并安装依赖mkdir movie_spider cd movie_spider python -m venv venvWindows 激活虚拟环境venv\Scripts\activateLinux / macOS 激活虚拟环境source venv/bin/activate激活后安装依赖pip install requests beautifulsoup4 pandas flask openpyxl依赖说明requests发起 HTTP 请求。beautifulsoup4解析 HTML。如果目标接口返回 JSON可以不使用但保留用于页面型站点。pandas生成 DataFrame导出 Excel。flash本地演示接口和爬虫 API 封装。openpyxlpandas 写 Excel 的后端依赖。3.3 开发工具建议优先使用 PyCharm 或 VS Code。命令行下直接运行 python 脚本也可以但建议在 IDE 里开启 Terminal方便观察 requests 日志和异常信息。4. 搭建本地公开数据演示站点爬虫练习最好不要上来就抓线上站点。先搭一个完全可控的本地接口能反复测试分页、超时、重试不会给任何真实服务器添麻烦。我下面用 Flask 做一个返回 JSON 的电影列表接口数据是本地虚构演示数据不指向任何真实片源。新建 app.pyfrom flask import Flask, jsonify, request app Flask(__name__) MOVIES [ { id: 1, title: 星际边界, year: 2024, score: 8.7, tags: [科幻, 冒险], intro: 一支科考队穿越未知星域尝试建立新的通信中继站。 }, { id: 2, title: 市井侦探, year: 2023, score: 7.9, tags: [悬疑, 剧情], intro: 老城区一家杂货铺老板用最朴素的推理破解连环案件。 }, { id: 3, title: 雪山回声, year: 2024, score: 8.2, tags: [纪录, 登山], intro: 记录了一支高山救援队在高海拔地区完成的极限任务。 }, { id: 4, title: 图书馆之夜, year: 2022, score: 7.6, tags: [奇幻, 家庭], intro: 三个孩子在闭馆后的图书馆里发现了一扇通往故事世界的门。 } ] app.route(/api/movies) def movies(): page int(request.args.get(page, 1)) page_size int(request.args.get(page_size, 2)) start (page - 1) * page_size end start page_size items MOVIES[start:end] return jsonify({ code: 0, message: ok, data: { page: page, items: items, total: len(MOVIES) } }) app.route(/) def index(): return h1公开数据抓取演示站点/h1p请访问 /api/movies/p if __name__ __main__: app.run(host127.0.0.1, port5000, debugFalse)启动服务python app.py浏览器访问 http://127.0.0.1:5000/api/movies?page1page_size2 能看到 JSON 数据。也可以直接用 curlcurl http://127.0.0.1:5000/api/movies?page1page_size2返回结果示例{ code: 0, message: ok, data: { page: 1, items: [ { id: 1, title: 星际边界, year: 2024, score: 8.7, tags: [科幻, 冒险], intro: 一支科考队穿越未知星域尝试建立新的通信中继站。 } ], total: 4 } }这个演示接口有分页参数但没有真实反爬策略。后面写爬虫时所有频率控制和重试逻辑都能在这套环境里验证。5. 编写 Python 爬虫主程序新建 spider.py。代码核心思路是每一页拿一批数据清洗后追加写入 CSV。请求失败先重试三次重试结束仍失败就跳过当前页不阻断整个任务。import csv import random import time from urllib.parse import urlencode import requests BASE_URL http://127.0.0.1:5000/api/movies OUTPUT_CSV movies.csv REQUEST_INTERVAL (0.5, 1.5) 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 ), Referer: http://127.0.0.1:5000/, Accept: application/json, text/plain, */*, } def fetch_page(page: int, page_size: int 2): 请求指定页并返回 JSON失败时自动重试 3 次。 params { page: page, page_size: page_size, } url f{BASE_URL}?{urlencode(params)} for attempt in range(1, 4): try: resp requests.get(url, headersHEADERS, timeout5) resp.raise_for_status() return resp.json() except requests.RequestException as exc: print(f第 {page} 页第 {attempt} 次请求失败: {exc}) time.sleep(1) return None def clean_movie(item: dict) - dict: 清洗单条电影数据统一字段格式。 tags item.get(tags) or [] if isinstance(tags, str): tags tags.split(,) return { id: item.get(id), title: str(item.get(title, )).strip(), year: int(item.get(year) or 0), score: float(item.get(score) or 0), tags: |.join(tags), intro: str(item.get(intro, )).strip().replace(\n, ), } def append_to_csv(rows: list): 增量写入 CSV使用 utf-8-sig 避免 Excel 打开乱码。 fieldnames [id, title, year, score, tags, intro] is_empty False try: with open(OUTPUT_CSV, r, encodingutf-8-sig) as f: is_empty f.readline() except FileNotFoundError: is_empty True with open(OUTPUT_CSV, a, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) if is_empty: writer.writeheader() writer.writerows(rows) def crawl(start_page: int 1, end_page: int 2): 按页码范围抓取数据并写入 CSV。 for page in range(start_page, end_page 1): data fetch_page(page) if not data: print(f第 {page} 页请求失败跳过该页。) continue items data.get(data, {}).get(items, []) if not items: print(f第 {page} 页无数据提前结束。) break cleaned [clean_movie(item) for item in items] append_to_csv(cleaned) print(f第 {page} 页完成新增 {len(cleaned)} 条记录。) # 随机延时模拟正常人浏览频率 time.sleep(random.uniform(*REQUEST_INTERVAL)) if __name__ __main__: crawl(start_page1, end_page2)运行脚本python spider.py运行成功后项目目录会多出一个 movies.csv内容包含表头和四行电影数据。打开文件看Chinese 不应该乱码每个字段都对齐在独立列里。这里有一个初学者容易踩的坑直接 print 字典终端里看到的是单引号或双引号混乱的 dict 文本看起来像乱码但不是乱码。写入 CSV 前要保证字段是 str、int、float而不是整个嵌套对象。上面 clean_movie 函数就是做这个事情。6. 批量抓取与断点续爬真实业务里一次性抓完所有页面通常不现实。网络抖动、目标服务偶发 500、本地电脑休眠、代码中途异常都可能让任务中断。因此批量爬虫要有两个能力从指定页码开始抓而不是每次从头开始。每写完一页就落盘避免在内存里累积大量数据后一次性写入。从crawl(start_page1, end_page2)这个方法签名可以看出通过修改 start_page就能实现手动续爬。比如第一轮抓到第 2 页第二轮想继续抓第 3 到第 10 页from spider import crawl crawl(start_page3, end_page10)更自动化的断点续爬可以用一个本地 JSON 文件记录进度import json import os STATE_FILE crawl_state.json def load_state(): if os.path.exists(STATE_FILE): with open(STATE_FILE, r, encodingutf-8) as f: return json.load(f) return {last_page: 0} def save_state(page: int): with open(STATE_FILE, w, encodingutf-8) as f: json.dump({last_page: page}, f, ensure_asciiFalse, indent2)每成功抓完一页后调用 save_state(page)。下次启动时读取 last_page从 last_page 1 开始。这种设计在长时间爬取中非常实用尤其是批量采集数据量大时重启成本会低很多。批量任务不能忽略的两个点请求失败不要盲目无限重试通常三次以内比较合适。每页之间要加延时。延时不等于“不给服务器压力”而是让你的请求更像真实用户。这里使用 random.uniform(0.5, 1.5)避免固定间隔被识别成机器行为。7. 数据导出 Excel 与接口 API 封装7.1 CSV 转 ExcelCSV 适合做中间存储交付给业务方时Excel 更方便阅读和筛选。安装 pandas 后可以用下面代码把 CSV 转成 xlsximport pandas as pd df pd.read_csv(movies.csv) df.to_excel(movies.xlsx, indexFalse, engineopenpyxl) print(Excel 导出完成: movies.xlsx)这段代码可以单独存成 export_excel.py 运行python -c import pandas as pd; df pd.read_csv(movies.csv); df.to_excel(movies.xlsx, indexFalse, engineopenpyxl)命令方式适合一次性执行脚本方式更适合集成到自动化流水线。7.2 把爬虫封装成 HTTP 接口如果你的爬虫最终要接进内部系统比如每天定时触发一次增量爬取可以把 crawl 函数包装成一个 Flask 接口。新建 run_api.pyfrom flask import Flask, request, jsonify from spider import crawl app Flask(__name__) app.route(/api/crawl, methods[POST]) def api_crawl(): body request.get_json(forceTrue) start_page int(body.get(start_page, 1)) end_page int(body.get(end_page, 10)) try: crawl(start_pagestart_page, end_pageend_page) return jsonify({code: 0, message: 爬取完成}) except Exception as exc: return jsonify({code: 1, message: str(exc)}), 500 if __name__ __main__: app.run(host127.0.0.1, port8000, debugFalse)启动接口服务python run_api.py用 curl 触发爬虫任务curl -X POST http://127.0.0.1:8000/api/crawl \ -H Content-Type: application/json \ -d {\start_page\: 1, \end_page\: 2}也可以用 Python 的 requests 调用import requests resp requests.post( http://127.0.0.1:8000/api/crawl, json{start_page: 1, end_page: 2}, timeout60, ) print(resp.json())这种封装模式的好处是上层调度系统不需要理解爬虫代码只要通过 HTTP 请求就能触发任务。适合 Jenkins、crontab、内部运营平台集成。生产环境需要注意接口不要直接暴露到公网。爬虫脚本本身能访问的目标数据可能涉及内部或半公开资源接口服务建议只监听 127.0.0.1或者放到内网网关后面配合鉴权使用。8. 反爬机制与合规应对很多人在真实抓取时会遇到 403、验证码、IP 封禁、数据返回异常。这些情况本质是目标站点在保护自己的资源和用户数据。应对思路应该是降低负载、保持合规而不是绕过访问控制。常见反爬原因和合规处理如下反爬现象常见原因合规处理思路403 Forbidden缺少请求头或 User-Agent 异常设置完整的浏览器请求头包括 UA、Accept、Accept-Language访问频率过高被封请求太快随机延时控制每秒请求数返回数据不完整接口有风控或登录要求重新确认数据是否真的公开必要时候申请官方数据权限验证码风控等级较高停止高频访问联系目标方授权或使用正规付费数据服务响应为空需要特定 Cookie确认是否需要登录态公开数据通常不应强制要求登录从材料来看很多初学者遇到的问题不是反爬有多厉害而是请求头不规范。requests 默认的 User-Agent 是 python-requests/x.x很多服务端会直接拦截。上面代码已经在 HEADERS 里伪装成 Chrome 浏览器请求头这一步不能省。另一个经常被忽略的是 Referer。有些接口会校验来源页面带上 Referer 可以避免刚开始请求就被拒绝。但 Referer 必须写真实来源不能随便填一个无关域名。需要强调的是合规爬虫的核心原则是数据源是否允许你这样抓取。如果一个视频网站展示给未登录用户的页面里包含付费内容入口但必须登录或付费才能观看那么你不能通过逆向接口、伪造请求、绕过参数校验来抓取完整内容。这属于绕过访问控制已经超出“爬虫技术学习”的范畴。9. 常见问题与排查方法9.1 PyCharm 爬虫运行后只显示 Process finished with exit code 0这是被问得最多的问题之一。先明确结论exit code 0 表示程序正常结束没有报错也不代表爬虫成功执行。通常有三种情况你写了函数但没有调用。你调用了请求函数但函数体内部没有输出也没有实际执行网络请求。代码存在 ifname main 分支但没进入该分支。排查方式很简单if __name__ __main__: print(start) crawl(start_page1, end_page2) print(end)如果只看到 start 和 end说明程序跑得太快请求可能没有真正执行。这时候重点检查函数缩进、条件判断、目标服务是否启动。9.2 其他常见问题问题现象可能原因排查方式解决方案爬虫程序运行不出内容没有调用入口函数检查是否写了if __name__ __main__在入口里调用并打印关键步骤启动后显示 ConnectionError目标服务未启动或地址错误浏览器访问地址先启动 app.py再运行 spider.py返回结果中文乱码终端编码或文件编码问题查看 CSV 编码写入文件用 utf-8-sig终端按需设置编码CSV 打开是空表字段名和表头不一致打印清洗后的字段检查 clean_movie 返回字段名请求返回 403缺少浏览器请求头打印 resp.status_code补上 User-Agent、Referer频繁被封请求频率过高查看日志请求时间间隔随机延时并降低并发API 调用失败端口占用或路由地址错误查看接口日志换端口或核对路径9.3 端口占用处理如果在启动 Flask 时提示端口被占用可以指定其他端口python app.py --port 5001代码里也可以改 app.run 的 port 参数。注意如果你修改了服务端口spider.py 里的 BASE_URL 也要同步修改。10. 爬虫项目最佳实践与提效建议10.1 项目目录结构建议按标准脚本工程组织而不是把所有代码堆到一个文件movie_spider/ ├── app.py # 本地演示数据源 ├── spider.py # 爬虫主程序 ├── run_api.py # 爬虫 HTTP API ├── export_excel.py # CSV 转 Excel ├── requirements.txt # 依赖清单 ├── movies.csv # 爬取结果 ├── movies.xlsx # Excel 导出文件 └── crawl_state.json # 断点续爬记录10.2 配置分离把 BASE_URL、延时区间、页大小、保存路径放到一个 config.py或直接读取环境变量import os BASE_URL os.getenv(MOVIE_BASE_URL, http://127.0.0.1:5000/api/movies) OUTPUT_CSV os.getenv(MOVIE_OUTPUT_CSV, movies.csv) MAX_RETRY int(os.getenv(MOVIE_MAX_RETRY, 3))这样做的好处是换环境时不需要改主代码配置文件即可。10.3 日志与失败记录不要用 print 代替日志。批量任务跑 1000 页print 的输出会非常长而且很难定位失败点。建议用 Python 内置 loggingimport logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(name)s: %(message)s, handlers[ logging.FileHandler(spider.log, encodingutf-8), logging.StreamHandler() ] ) logger logging.getLogger(spider)使用 logger.info 替代 print可以在日志文件中保留每个页面的完成状态。失败时把异常写入日志而不是让整个程序静默退出。10.4 增量抓取与去重如果目标数据会更新建议每次抓取前先读取已有 id 集合只抓新增内容。去重逻辑可以放在 append_to_csv 前def load_existing_ids(): if not os.path.exists(OUTPUT_CSV): return set() df pd.read_csv(OUTPUT_CSV) return set(df[id].dropna().astype(int))然后检查目标 id 是否在集合中存在跳过该条。不存在清洗后写入并加入集合。这样重复运行脚本不会产生重复记录。10.5 合规留存建议保留抓取日期、数据来源 URL、数据版本、目标站点许可状态。一旦后续发生数据合规争议完整的采集合规记录可以帮助判断边界。11. 总结与下一步这套 Python 爬虫案例最值得学习的不是复杂框架而是完整的工程链路从数据源搭建、请求封装、字段清洗、增量写入到批量续爬、Excel 导出和 HTTP API 封装。只要把基础链路跑通换成其他公开 JSON 接口时基本就是改 URL 和字段映射。第一时间应该验证的是本地演示接口能不能正常返回数据以及 spider.py 跑完后 movies.csv 是否包含预期字段。最容易踩的坑就是 HTTP 服务没启动就运行爬虫或者只写完 def crawl 却忘了在入口处调用。后续可以做的方向还有很多把 csv 存储换成 MySQL 或 SQLite、把单机脚本改成 Scrapy 项目、给接口服务加上简单的 Token 鉴权、把爬虫调度接到定时任务里。只要每一层都围绕“合规、稳定、可维护”展开爬虫技术就能在数据分析、自动化测试、业务监控等方向发挥价值。建议把这篇文章先收藏按步骤跑通后再去研究 Scrapy、异步爬虫等内容。真正稳定的爬虫项目永远是在小规模、低频率、合法授权的前提下逐步完善的。
返回列表