ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

【第44期】Python 电影信息抓取:requests、JSON 清洗、限速和合规边界

【第44期】Python 电影信息抓取:requests、JSON 清洗、限速和合规边界 【第44期】Python 电影信息抓取requests、JSON 清洗、限速和合规边界CSDN 完整教程系列《从小白到 AI 大模型开发工程师的进阶之路》技术点AI-0132 网页电影信息抓取主人公小蓝伞前置AI-0128 requestsAI-0131 文件保存本期产出结构化数据文件第 40 期会发请求第 43 期会可靠落盘。小蓝伞想抓“电影信息”做练习第一反应是用 BeautifulSoup 去刮商业网站真正运行时搜索页偶尔返回登录 HTML代码却把它当 JSON最终生成了空的movies.json。这期先把边界钉死学习项目优先公开 API、开放数据或本地夹具没有授权就不解析商业页面。本期交付一个只采集最小字段的命令行程序以及可离线复现的成功、403、非 JSON、超时四类响应。真实外网状态随时会变所以网络步骤统一标为【建议验证】不能拿某次成功承诺长期可用。一、小蓝伞遇到的问题目标输入电影名得到标题、年份、摘要保存 UTF-8 JSON。风险无 timeout 的请求卡住把 HTML 当 JSON 解析没有 User-Agent 和间隔触发 429把有版权的海报和剧情大段转存到公开仓库建议插图 1公开 API、本地夹具、禁止的商业站点 HTML 三条路径。二、先给结论抓取首先是授权和契约问题其次才是解析问题。优先https://api.tvmaze.com/search/shows?q这类公开 JSON API或本地夹具。必须 timeout、检查状态码、校验结构。连续请求加间隔尊重 robots 和站点条款。只保存练习需要的字段不把整页 HTML 入库。网络失败时用本地样本保证测试不依赖外网。三、本文要解决什么项目内容输入电影/剧名字符串输出movies.json成功判据正常名返回列表空名失败非 JSON 失败超时可捕获不在范围登录绕过、验证码、大规模爬虫四、前置准备python-m pip install requests mkdir D:\ai-learning\issue-44不要提交 API Key。TVMaze 公开搜索不需要 Key仍要限速。五、核心原理一次请求至少有四层契约。传输层要求在超时时间内拿到响应HTTP 层要求状态码符合预期媒体层要求Content-Type是 JSON数据层要求顶层和字段结构正确。200只通过第二层网关完全可能用 200 返回 HTML 提示页。直接调用response.json()后捕获所有异常并返回[]会把“请求失败”伪装成“没有搜索结果”与上一期把坏 JSON 当空待办是同一类错误。清洗不等于删除所有标签。摘要字段可能为null、HTML 片段或超长文本应先校验类型再用标准解析器提取文本合并空白并限制长度。简单的.replace(p, )只能删固定标签遇到b、实体或属性会残留。本文使用标准库html.parser避免为了三个字段增加依赖复杂页面应在得到许可后选成熟解析库。限速也不是固定sleep(1)就万事大吉。单次命令只发一个请求批量调用需要由上层串行控制遇到 429 读取Retry-After或采用有上限的指数退避。不要换 IP、伪造 Cookie 或绕验证码。robots.txt由 RFC 9309 定义的是爬虫访问规则不是版权许可证即使规则允许也仍要阅读 API/站点条款并遵守数据用途限制。正确的数据流应是授权数据源 - timeout - 状态码 - Content-Type - JSON 结构 - 字段最小化 - 临时文件 - 原子替换 - 可离线复验六、完整项目目录结构issue-44/ ├── movie_fetcher.py └── fixtures/ └── search_ok.jsonfixtures/search_ok.json使用虚构数据避免把真实服务响应整包提交到仓库[{show:{name:Blue Umbrella Lab,premiered:2026-09-21,summary:pA bfictional/b learning sample./p}}]movie_fetcher.pyfromhtml.parserimportHTMLParserfrompathlibimportPathimportargparseimportjsonimportosimporttempfileimportrequests OUTPath(__file__).resolve().parent/movies.jsonAPIhttps://api.tvmaze.com/search/showsUA{User-Agent:ss-csdn-issue-44-learning/1.0,Accept:application/json}classTextExtractor(HTMLParser):从允许处理的摘要片段中提取纯文本。def__init__(self)-None:super().__init__()self.parts:list[str][]defhandle_data(self,data:str)-None:self.parts.append(data)defclean_summary(value:object)-str:ifnotisinstance(value,str):returnparserTextExtractor()parser.feed(value)return .join(.join(parser.parts).split())[:200]defnormalize(data:object)-list[dict]:校验响应结构只保留练习需要的三个字段。ifnotisinstance(data,list):raiseValueError(响应顶层必须是列表)rows[]foritemindata:showitem.get(show)ifisinstance(item,dict)elseNoneifnotisinstance(show,dict)ornotisinstance(show.get(name),str):continuerows.append({name:show[name].strip(),premiered:show.get(premiered),summary:clean_summary(show.get(summary)),})returnrowsdefsearch_show(name:str,session:requests.Session)-list[dict]:qname.strip()ifnotq:raiseValueError(名称不能为空)respsession.get(API,params{q:q},headersUA,timeout(3.05,10))resp.raise_for_status()content_typeresp.headers.get(Content-Type,).lower()ifjsonnotincontent_type:raiseValueError(f响应不是 JSON{content_typeor未知类型})returnnormalize(resp.json())defload_fixture(path:Path)-list[dict]:returnnormalize(json.loads(path.read_text(encodingutf-8)))defsave_rows(path:Path,rows:list[dict])-None:payloadjson.dumps(rows,ensure_asciiFalse,indent2)fd,temp_nametempfile.mkstemp(dirpath.parent,suffix.tmp)try:withos.fdopen(fd,w,encodingutf-8)asfile:file.write(payload)file.flush()os.fsync(file.fileno())os.replace(temp_name,path)exceptException:ifos.path.exists(temp_name):os.remove(temp_name)raisedefmain()-int:parserargparse.ArgumentParser()parser.add_argument(query,nargs?)parser.add_argument(--fixture,typePath)argsparser.parse_args()try:ifargs.fixture:rowsload_fixture(args.fixture)else:ifargs.queryisNone:raiseValueError(联网模式必须提供搜索词)withrequests.Session()assession:rowssearch_show(args.query,session)save_rows(OUT,rows)print(f保存{len(rows)}条到{OUT})return0except(OSError,ValueError,requests.RequestException,json.JSONDecodeError)asexc:print(f抓取失败{exc})return2if__name____main__:raiseSystemExit(main())先跑完全离线、结果固定的路径python movie_fetcher.py--fixture.\fixtures\search_ok.json python-m json.tool.\movies.json预期显示保存 1 条摘要为A fictional learning sample.文件里只出现三个目标字段。联网路径为python movie_fetcher.py Person of Interest【建议验证】。执行前重新查看 TVMaze API 条款与当前限制本文没有把 2026-09-21 某次响应当永久保证。七、可复现失败案例项目记录环节记录——构造方式假响应设置403、text/html正文为登录提示故障现象旧代码在json()报JSONDecodeError又被宽泛异常吞掉并保存[]影响权限失败被报成“无搜索结果”旧的有效文件还被空列表覆盖最初误判页面编码错误准备强制改成 UTF-8排查顺序状态码 - Content-Type - 响应前 200 字符 - JSON 顶层类型根因把 HTTP 成功、JSON 媒体类型和业务结构混成一个判断修复分层校验任何失败都不调用save_rows复验403、HTML 200、字典顶层分别失败原movies.json哈希保持不变失败样本只保存虚构片段真实响应可能包含账号、请求 ID 或网关信息不应直接贴到文章或公开仓库。八、实验设计与数据实验分离网络与解析。离线组固定夹具和假响应可在 CI 重复联网组只做手工连通性验证不作为测试必须通过的门槛。指标是退出码、保存条数、字段集合以及失败前后输出文件哈希。输入预期是否允许写文件合法列表夹具1 条、3 个字段、摘要无标签是空搜索词ValueError否200 text/html媒体类型错误否200 JSON 字典顶层结构错误否403 HTMLHTTP 错误否连接/读取超时RequestException否列表内缺少show.name跳过坏项并统计是这组实验能证明“失败不会伪装成空结果”不能证明远端 API 永久稳定也不能证明固定 10 秒适合所有网络。连接与读取使用不同 timeout是因为 DNS/TCP 建连慢和服务器响应慢是两类故障生产还应补重试预算、监控和缓存。九、常见问题与避坑为了消除证书错误设置verifyFalse。这会放弃服务端身份校验。应修复证书链或代理配置而不是隐藏风险。把 429 当普通失败立即重试。连续重试会加重限流。遵守Retry-After达到预算就停止。提交 Cookie、Token 或完整响应。它们可能包含凭证和个人信息。敏感配置放环境变量日志只留必要字段。选择器失效就不断加兼容分支。如果没有授权和稳定契约正确动作是停止并换公开数据源。摘要清洗只做字符串替换。标签和实体有多种形态应使用解析器并在输出侧限制字段长度。十、平台、系统与库的差异requests 是第三方库标准库 urllib 也能请求但异常、会话和超时接口不同不能直接复制代码。Windows 企业环境常有 HTTPS 代理或自签 CALinux 容器常缺代理配置云函数共享出口更容易触发限流。它们只改变部署与网络行为不改变四层校验。BeautifulSoup 只在处理自己或明确授权的 HTML 时使用。开发机、CI 和无网环境都应靠同一份离线夹具验证数据契约。十一、验证清单离线命令退出码为 0输出恰好 1 条字段集合只有name/premiered/summary。摘要中不应残留p、b长度不得超过 200 个字符。空搜索词、HTML 响应和字典顶层均返回退出码 2旧文件哈希不变。超时参数必须同时包含连接和读取上限代码中不得出现verifyFalse。仓库搜索不到 Cookie、Token、真实账号和整页 HTML。联网验证若执行要记录日期、数据源和结果失败只记“网络待复核”不能改写成 API 无效。批量扩展前先确认当前条款、频率限制与数据许可并把停止条件写进程序。让数据来源可以追溯只保存 name、premiered、summary 还缺一层工程信息这些字段从哪里来、何时取得、按哪个清洗版本生成。练习可在文件外维护 manifest记录 source、fetched_at、query、schema_version 和程序版本不要把完整响应或敏感请求头塞进去。某天摘要变化时读者能判断是远端更新、清洗规则变化还是同名条目匹配错误。没有来源元数据的数据即使格式整齐也很难复核。缓存与重试要遵守同一个授权边界。短时间重复查询可读取本地缓存减少对远端的压力缓存键至少包含标准化查询词和数据源版本缓存项要有过期策略。重试只适用于连接重置、部分 5xx 或明确允许的 429400、401、403 通常需要修正请求或权限自动重试没有意义。每次重试都应占用统一预算并带抖动避免多个进程同一时刻再次冲击服务。可观察性不要记录隐私。建议记录请求目标的主机、状态码、耗时、重试次数、返回条数和错误分类不记录 Cookie、Authorization、完整查询词或原始正文。错误正文若必须留作排查先截断、脱敏并限制访问教学项目最好只用虚构假响应。日志里的“0 条”必须区分为合法空列表与请求失败不能复用同一个成功事件。结构演进也需要门禁。远端新增字段不应导致失败因为本文只读取白名单关键字段类型改变、顶层从列表变字典则必须停止。可以用一小组离线契约样本覆盖最小合法项、含额外字段、summary 为 null、缺 name、顶层错误。每次改清洗器先跑这些样本再做一次手工联网验证。这样外网不可用时仍能开发外网恢复后又能检查真实契约是否漂移。不要把抓取速度当唯一指标。一个每秒一百请求却产生错误数据、违反条款、无法追溯的程序比每秒一个请求更差。验收顺序应是授权成立、失败可见、字段正确、写入可恢复最后才是吞吐。若业务确实需要大规模数据优先寻找批量下载、开放数据集或正式商业接口而不是把学习脚本加并发后直接运行。用假会话覆盖网络失败离线测试不必真的启动 HTTP 服务。定义一个 FakeResponse提供 status_code、headers、json 与 raise_for_statusFakeSession 的 get 记录 URL、参数、请求头和 timeout再返回预设响应或抛 requests.Timeout。这样可以断言代码确实传了(3.05, 10)Accept 为 JSON空查询甚至没有发起 get。假对象只模拟使用到的窄接口越小越容易看出测试和真实 requests 是否偏离。成功样本要验证清洗结果而不是只断言列表非空name 去掉首尾空白summary 去标签并合并空格null 变空串长度不超过 200。混入一个没有 name 的坏项后本文选择跳过更严格的业务可以整批拒绝或把坏项写入隔离区但必须统计跳过数。否则数据文件看似成功实际上每天悄悄少记录。失败样本要验证保存函数没有被调用。可以预先写入old文件令响应为 403、HTML、字典顶层或超时运行后比较原文件字节。若程序先清空输出再请求所有异常处理都来不及挽救旧数据。请求、规范化、保存三段函数分开正是为了让这个边界可以单独断言。真实联网验证只回答“此刻从这台机器能否按当前契约访问”。记录响应状态、媒体类型、条数和日期即可不把整包数据纳入测试快照。快照会迅速过期也可能重新分发不该保存的内容。远端失败时先检查 DNS、代理、证书、状态码和配额再判断是否是程序字段变化不要一看到 JSONDecodeError 就修改编码。如果以后增加分页循环必须有页数或记录数上限并在重复游标时停止。最后一页为空、next 缺失、游标循环是三种不同终止条件。未经验证的无限分页加自动重试会把小练习变成对远端持续施压的程序。合规边界必须落实为代码中的速率、预算和停止条件。保存前还应对记录做稳定排序或明确保留远端顺序否则同一批数据只因返回顺序变化就产生整文件 diff。若 name 不能唯一标识条目应保存数据源提供的稳定 ID用标题去重会把同名作品错误合并。本文为最小练习没有加入 ID 与分页扩展时应先修改输出 schema、夹具和迁移说明再增加请求数量。数据质量报告可以只包含四个数字收到条数、成功规范化条数、跳过条数、最终写入条数。四者关系不成立就拒绝保存。相比在日志里打印每条完整记录这组聚合既便于发现字段漂移也减少敏感内容暴露。错误率超过阈值时应停止而不是继续产出一份看似合法但大量缺行的 JSON。十二、面试题与追问为什么先 API 后 HTML契约稳定、授权清晰。追问HTML 永远不能用吗有书面授权或自己的页面可以。拿到 200 就成功了吗没有还要结构。追问和第 40 期关系同一条。429 怎么办降速、退避、停。追问换 IP 绕过学习项目禁止。如何让测试不依赖外网夹具和 fake session。追问那还要不要真实请求手工【建议验证】。保存 HTML 全文有什么问题版权、体积、结构不稳定。追问摘要截断到 200 字够吗练习够产品要看条款。十三、小蓝伞的工程金句抓取首先是授权问题其次才是解析问题。能离线测通的抓取才配进合集。200 只说明传输成功不说明数据可用。十四、本篇技术清单与下一期下一期AI-0133 简单计算器与数字游戏输入校验和随机数不碰网络。关注合集。你被 429 或 HTML 当 JSON 坑过吗先问能不能抓再问怎么抓。官方资料https://www.rfc-editor.org/rfc/rfc9309.htmlhttps://requests.readthedocs.io/en/latest/https://www.tvmaze.com/api适用边界学习与个人练习。商业数据以对方条款为准。
返回列表