
Python网络爬虫是 Python 学习路线里最让人上头的方向之一同时也是被误读得最厉害的方向。很多人一听说爬虫第一反应就是“抓包、破解反爬、换 IP、防止封号”好像爬虫天生就是和网站搞对抗的。实际做了几个项目之后你会发现真正决定一个爬虫脚本能不能长期稳定跑下去的根本不是你会不会绕过某个防护而是你有没有把请求、解析、调度、日志、重试这些工程细节理顺。这篇文章我会按实际落地的顺序把 Python 网络爬虫从抓包请求到数据解析、从单任务到批量、从代理到合规边界完整拆一遍。适合正在学 Python 基础、准备上手爬虫的读者也适合已经写过一些脚本但经常遇到反爬、封号、批量任务跑挂的人。先给一个核心结论爬虫最重要的能力不是“绕过”而是“理解”。理解数据怎么传输、页面怎么渲染、网站为什么限制请求、请求频率和服务器负载之间是什么关系。这些理解到位之后很多需求用 requests 加 BeautifulSoup 就能解决根本不需要一上来就上代理池、并发框架和分布式抓取。1. 先想清楚你要的是数据不是“破解反爬的技术”1.1 爬虫的本质是自动获取公开数据爬虫解决什么问题一句话把本来需要在浏览器里手动重复做的“打开页面、复制内容、粘贴到表格”变成脚本自动执行。它节省的是人力替代的是重复操作并不是“窃取保密数据”的武器。学习爬虫的第一步不是装工具而是建立基本认知你写的每一行代码都是在对某个服务器发起请求。服务器返回数据背后有带宽成本、计算成本和运营成本。所以在设计爬虫时最需要关注的指标不是“爬得多快”而是“有没有给别人造成负担”。一个请求出去服务器要响应数据库要查询日志要记录。如果你用 100 个线程同时打一个网站等于用 100 个访客把小型站点压到无法响应。这不是技术能力强而是工程意识弱。1.2 边界判断哪些数据能爬、哪些不能碰开始动手之前先回答三个问题数据是不是公开可访问的不需要登录、不需要权限、不涉及个人隐私和账号体系这类数据适合学习和练习。网站是否有 robots.txt 或服务条款说明很多站点的 robots.txt 会明确写出哪些路径允许爬取哪些不允许。你的请求频率是否控制在合理范围手动浏览的速度是每秒几次脚本也应该接近这个量级。不适合作为学习素材的数据包括需要账号密码才能访问的私密页面、包含手机号或身份证等个人信息的内容、明确在服务条款里禁止抓取的数据、以及任何付费内容。碰到这些场景正确做法是直接放弃或者改用官方 API。很多网站提供开放接口这本身就是给开发者用的优先用接口永远比硬爬页面更省事。1.3 为什么一上来就学“绕过”是最差的学习路径市面上很多教程把“反爬绕过”当成卖点好像学会了就能随意取数据。这种学习路径有几个问题第一它偏离了爬虫的本质。爬虫的难点从来不是“绕过限制”而是“稳定获取数据”。你即使绕过了 UA 检测、验证码和频率限制如果不会写健壮的解析逻辑不会处理网络波动和页面结构变化数据照样拿不全。第二它容易让你养成坏习惯。一遇到网站有防护就想着硬碰硬而不是停下来思考“我是不是频率太快了”“我是不是不应该访问这个路径”。很多封号问题根本不是 IP 被盯上而是请求行为异常比如一秒几十次请求、同一时间访问大量不相关页面、请求头缺字段。第三合规风险。爬虫本身不是违法行为但哪些能爬、哪些不能爬是有边界的。把时间花在破解防护上不如把时间花在理解协议、理解数据、理解工程化上。所以这篇文章的顺序是环境 → 单条请求 → 抓包 → 解析 → 理解反爬 → 批量 → 实战 → 排查。按这个顺序学后面每一步都会轻松很多。2. 环境与最小爬虫先把技能栈跑通2.1 安装 Python 和虚拟环境不管你是 Windows、macOS 还是 Linux第一步都是装好 Python。下载安装包之后建议勾选“Add Python to PATH”否则后面在命令行里敲 python 会提示找不到命令。装完以后在终端里验证python --version能看到版本号就说明安装成功。如果系统里同时存在 Python 2 和 Python 3可能需要用 python3 命令区分。接下来创建虚拟环境。虚拟环境的作用是隔离项目依赖防止不同项目之间包版本冲突。这一步看着多此一举但等你同时维护两三个爬虫项目时就知道有多重要了。python -m venv venvWindows 激活venv\Scripts\activatemacOS / Linux 激活source venv/bin/activate激活之后命令行前面会出现 (venv) 前缀说明当前已经在虚拟环境里。之后安装的包都会装到这个环境内部不会污染全局 Python。2.2 安装 requests、BeautifulSoup 和 lxml爬虫最常用的库有三个requests发 HTTP 请求代替浏览器去访问页面或接口。beautifulsoup4解析 HTML 页面帮你从一堆标签里找到想要的内容。lxml一个底层的 HTML/XML 解析引擎配合 BeautifulSoup 使用解析速度更快。安装命令pip install requests beautifulsoup4 lxml如果下载速度慢可以临时换成国内镜像源pip install requests beautifulsoup4 lxml -i https://pypi.tuna.tsinghua.edu.cn/simple这里要注意换镜像源只影响下载速度不影响代码运行。2.3 写第一个最小爬虫安装完成后别急着写复杂的项目。先写一个最小脚本目标是访问一个公开页面拿到标题打印出来。import requests url https://example.com resp requests.get(url, timeout10) print(resp.status_code) print(resp.text[:500])运行后你能看到两样东西HTTP 状态码和页面源码前 500 个字符。状态码是判断请求是否成功的第一指标200成功。301 / 302重定向需要跟踪或手动处理。403服务器拒绝请求可能是反爬也可能是没有权限。404路径不存在。429请求太频繁被限流了。500 / 502 / 503服务器端出错也可能是不稳定。接着用 BeautifulSoup 解析标题from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) title soup.title.get_text() print(title)如果页面结构正常这段代码会输出页面的 title 内容。到此你已经完成了一个最小爬虫的闭环请求 → 响应 → 解析 → 输出。2.4 怎么确认这次请求成功了很多新手看到 resp.text 有内容就觉得成功了其实不一定。判断请求是否成功不能只看“有返回”要看三点状态码是否符合预期。比如 200 是正常但 200 也可能返回一个统一错误页。返回内容里是否包含目标数据的关键标记。比如爬一个商品列表页源码里应该出现商品名、价格、链接等关键词。是直接返回的 HTML还是需要通过接口异步加载的 JSON。如果是后者直接解析 HTML 会什么都拿不到。我一般会先在脚本里打印 resp.text[:200]快速扫一眼返回内容再决定下一步。不要盲目相信代码里写的 URL 和解析表达式页面结构随时可能变化。3. 抓包请求从浏览器开发者工具开始3.1 Network 面板是抓包的第一课所谓抓包就是查看浏览器在请求某个页面时到底向服务器发送了什么数据、接收了什么数据。最简单的抓包工具就是浏览器自带的开发者工具按 F12 打开切到 Network网络面板刷新页面就能看到所有网络请求。第一次看 Network 面板很多人会觉得很乱因为一个页面往往有几十个请求图片、 CSS、JS、字体、接口全在里面。不用慌只需要关注 XHR / Fetch 类型的请求。这类请求通常是页面异步加载数据时发出的很多网站的实际数据都来自这些接口。具体操作步骤打开开发者工具切到 Network。选中 Fetch/XHR 过滤条件。刷新页面观察新出现的请求。点击某个请求查看它的 Headers、Payload、Response。Headers 里能看到请求地址、请求方法、User-Agent、Cookie、Referer 等字段Payload 里能看到发送给服务器的参数Response 里能看到服务器返回的数据。把这些字段复制到你的 Python 代码里就能用 requests 模拟同一个请求。3.2 从请求 URL 和 Headers 还原一个接口举个例子。你在网页上看到一个新闻列表翻页时列表自动刷新了。打开 Network 面板会看到一个类似 news/list?page2categorytech 的请求。点开 Response发现返回的是 JSON 数据里面是新闻标题、时间和链接。这时候要做的是看清楚请求方法是 GET 还是 POST。记录 URL注意参数变化。翻到第 3 页URL 里的 page 参数是不是变成了 3。记录请求头。有些接口不设置 User-Agent 或 Referer服务器会拒绝。记录请求体。POST 请求通常有表单参数需要在代码里同步带上。还原成 requests 代码大致是import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://example.com/news, Accept: application/json, } params { page: 2, category: tech, } resp requests.get( https://example.com/news/list, paramsparams, headersheaders, timeout10, ) print(resp.status_code) print(resp.text)注意这里我给的是通用示例具体 URL、参数名、请求头要以你实际抓到的为准。不同网站的接口设计差别很大。3.3 requests 模拟请求时最容易漏的三个参数第一个是 timeout。requests 默认不设置超时遇到网络波动或服务器不响应时脚本会一直挂在那里。生产环境里一定要加 timeout同时配合 try/except 捕获异常。第二个是 headers。很多接口只接受带浏览器标识的请求没有 User-Agent 会被直接拒绝。更复杂的接口还会校验 Referer表示请求是从哪个页面跳转过来的。第三个是 params 和 data 的区别。GET 请求的参数放在 URL 后面用 paramsPOST 请求的参数放在请求体里用 data 或 json。用错了接口就会返回参数错误。3.4 动态页面先找接口别先啃 JS有些页面是动态渲染的打开源码只能看到空壳真实数据是由 JavaScript 发请求后填充的。这种情况下正确思路是去 Network 面板里找 XHR/Fetch 接口直接请求接口拿 JSON而不是去分析 JS 逻辑。抓包能力练到这一步你就已经能解决大部分“页面源码没有数据”的问题了。记住一个原则浏览器能展示的数据一定经过了网络请求。找到那个请求就等于找到了数据源头。4. 数据解析正则、XPath、BeautifulSoup、JSON 怎么选4.1 正则小场景够用别硬撑正则表达式适合处理简单、格式固定的文本提取。比如从一段字符串里提取所有日期、提取 img 标签里的图片链接、提取纯数字 ID。但正则不太适合处理复杂的 HTML 嵌套结构。HTML 标签层级多、属性顺序不固定用正则硬解析很容易出错而且代码可读性会很差。正则匹配到的内容往往还需要二次清洗维护成本高。我的建议是能用解析库的地方优先用解析库。正则只用来处理解析库不好表达的纯文本场景比如在一段 JSON 字符串里提取某个字段或者在日志文本里筛查关键字。4.2 XPath 与 BeautifulSoup页面解析的主力BeautifulSoup 是新手最友好的解析库。它的思路是把 HTML 转成一棵树然后按标签名、class、id、属性去查节点。示例from bs4 import BeautifulSoup soup BeautifulSoup(resp.text, html.parser) title soup.select_one(h1.article-title).get_text() links soup.select(div.list a) for link in links: print(link.get(href), link.get_text())select 和 select_one 用的是 CSS 选择器语法。熟悉前端选择器的话上手非常快。select_one 返回第一个匹配节点select 返回所有匹配节点列表。XPath 则是另一种定位方式表达能力更强。它能按文本内容、位置、属性关系定位节点例如“找到第三个 div 下所有 class 以 item 开头的 a 标签”。lxml 本身支持 XPath也可以配合 BeautifulSoup 使用。from lxml import etree html etree.HTML(resp.text) items html.xpath(//div[contains(class, item)]//a/href) for href in items: print(href)XPath 的优点是定位精确缺点是语法稍复杂。新手可以先从 BeautifulSoup 的 CSS 选择器入手遇到定位不准的情况再换 XPath。4.3 JSON接口返回数据最稳定的解析方式如果页面数据是通过 XHR/Fetch 接口加载的返回的数据通常是 JSON 格式。JSON 的结构非常稳定解析起来也最简单import json data json.loads(resp.text) news_list data[data][list] for news in news_list: print(news[title], news[url])json.loads 把 JSON 字符串转成 Python 字典或列表之后按 key 取字段就行。这里要养成先查看返回结构的习惯把 json 数据完整打印出来再决定怎么取字段。如果返回的数据量很大可以用 json.dumps(data, ensure_asciiFalse, indent2) 格式化打印这样嵌套结构一目了然。注意 JSON 里的 key 名不一定和页面显示的文字一致要以实际返回为准。4.4 解析方式选型判断表场景推荐方案原因页面是静态 HTML层级简单BeautifulSoup CSS 选择器代码直观容易调试页面 HTML 复杂需要按位置或属性精确定位XPath定位能力强过滤条件多数据来自异步接口返回 JSONjson 模块直接解析结构稳定不需要处理 HTML 标签从纯文本中提取固定格式内容正则表达式轻量、直接数据是表格、嵌套层级很深优先考虑 JSON 或 XPath避免多层遍历导致性能下降判断标准很简单数据是接口来的就解析 JSON数据是页面里的就先用选择器定位选不中再考虑 XPath。不要在一个页面里混用多种解析方案代码会越来越难维护。5. 反爬不是敌人理解网站的保护逻辑5.1 常见反爬机制到底有哪些先明确一点网站做反爬是为了保护服务器稳定和数据不被滥用不是专门针对你。常见的机制有下面几类User-Agent 检测检查请求头里的浏览器标识识别非浏览器请求。频率限制同一 IP 在短时间内大量请求触发限流返回 429 或验证码。Cookie / 登录墙部分数据需要登录后才能看到。验证码高频访问或异常行为时弹出要求人机验证。动态渲染数据不在初始 HTML 里需要浏览器执行 JavaScript 才能看到。请求参数校验接口要求特定签名、时间戳或加密参数。这些机制本身都是合理的防护手段。学习爬虫的时候应该理解它们存在的意义而不是一上来想着怎么绕过。真正的工程姿势是降低请求频率、模拟正常浏览器行为、优先使用官方 API、尊重网站的访问规则。5.2 User-Agent、Referer、Cookie 的正确理解User-Agent 是请求头里的一个字段表示“我是谁”。浏览器访问网站时会自动带上完整 UA 字符串。requests 默认的 UA 是 python-requests很多服务器会根据这个识别脚本。设置一个常见 UA本身是合理行为因为这意味着你在模拟普通访客的标准请求头headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/, }Referer 表示请求是从哪个页面发起的。有些接口会校验 Referer防止跨站调用。Cookie 用于维持会话状态登录后的页面请求往往需要带上登录后生成的 Cookie。但要注意设置这些字段只代表“让请求更像正常访客”不代表你可以无视网站的频率限制。请求头写得再漂亮一秒请求几十次照样会被限流。5.3 Session 和登录态requests 的 Session 对象可以自动保存 Cookie适合需要连续访问多页面的场景。比如先访问首页再访问详情页服务器可能会在第一次响应时设置 Cookie用它确认你的会话。session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), }) resp session.get(https://example.com, timeout10)对比直接使用 requests.getSession 的好处是跨请求保持 Cookie让服务器认为“还是同一个人”。这在处理登录态、分页请求时非常有用。如果有登录需求先确认网站是否提供官方 API 或开放授权方式。没有官方途径时不要轻易尝试用脚本处理需要账号密码的登录流程一方面容易触发风控另一方面涉及账号安全和个人隐私合规问题。5.4 频率控制限速是对双方的保护频率控制是爬虫工程里最重要的参数之一。它不是一个固定值而是根据目标网站的规模和响应速度动态调整。判断依据如果你请求一个页面响应时间是 0.5 秒那么每秒最多请求 2 次就已经算极限了。如果你同时访问多个页面总请求频率要控制在网站能承受的范围内。如果日志里出现大量 429、503 或连接超时说明频率太高需要降低并发而不是马上去换 IP。常用的限速方法是在两次请求之间加 time.sleepimport time time.sleep(1)简单直接适合单线程脚本。并发场景下需要用队列和令牌桶来控制整体速率而不是在每个线程里各自 sleep。记住一句话限速永远优先于换代理。请求频率正常、行为规范的情况下大多数网站不会封你。很多封号问题的根源不是 IP 不好而是请求行为异常。6. IP代理和“防封”的真相它不是护身符是工程组件6.1 代理解决的真正问题是什么IP 代理在网络编程里本来就是一个标准组件用于请求转发和出口 IP 管理。在爬虫场景里代理解决的真正问题不是“突破封锁”而是以下两类需求一是分布式采集时的出口 IP 管理。当你好几台服务器或容器都在跑爬虫任务时如果使用同一出口 IP任何防火墙都会怀疑。用代理池把请求分散到多个出口 IP是合理的工程做法。二是访问公开数据时的地域分配。有些公开数据服务按地域分配节点合理使用代理可以匹配到对应区域的节点。但这里要特别提醒任何情况下都不要用代理去访问没有权限的内容或者规避平台的正当限制。对于绝大多数练习项目和学习场景根本不需要代理。如果脚本频繁被 429 限流第一反应应该是降低频率而不是上代理。6.2 什么时候才需要考虑代理池只有满足以下条件时才需要考虑代理池你已经把请求频率降到很低确认是单 IP 级别的频率限制。你的任务量确实很大比如需要长时间抓取大量公开数据单 IP 无法完成。你有合法的采集目标和权限比如采集自己网站的统计数据、采集公开的行业信息。目标网站的服务条款允许这种采集行为。如果不满足这些条件最可能的结论是“这个数据不该频繁抓取”而不是“该换代理了”。6.3 代理池的一般设计思路代理池本身的架构并不神秘核心就几件事代理来源、可用性鉴定、按策略分配、自动清理失效代理。一个简化设计思路如下从合法渠道获得一批 HTTP/SOCKS 代理地址。用一个检测脚本定时测试每个代理的连通性、响应速度和匿名级别。把可用代理放入队列按轮询或随机策略分配给请求。某代理连续失败时自动标记为失效并移除。设置超时时间和重试次数避免单次请求卡死。这里只讲设计思路不展开具体代码。因为代理池的性能跟代理质量、目标网站、网络环境强相关脱离具体场景写出来的代码没有参考价值。更重要的是大多数个人项目根本不值得搭建代理池一个限速合理的单线程脚本往往就够用了。6.4 必须说清楚的合规边界关于代理有几个边界必须明确说清楚。代理不是用来规避法律的不是用来突破平台限制的不是用来掩盖违规行为的。如果你发现自己的爬虫需要用代理才能“防止被封”先停下来想想频率是不是太高了访问内容是不是不合适网站条款是否允许在博客、博客园、CSDN 这类技术社区里讨论代理池的架构设计是正常的技术交流因为代理是网络编程的基础组件。但把代理包装成“绕过封禁、防封号”的灰色工具这个方向我不推荐也不符合主流技术社区的价值导向。一句话总结代理是工程组件不是护身符。能不用代理解决的需求就不要用代理必须用代理的场景也要确保采集行为本身合规。7. 批量任务与实战项目从能跑到能稳定跑7.1 单条任务跑通后再谈批量很多人学爬虫一开始就想着多线程、多进程、异步并发结果代码跑起来一片混乱。我的建议很直接先单条任务跑通再谈批量。单条任务跑通的标志是什么打开一个公开页面能按预期拿到数据输出格式正确错误信息能看懂。这一步没做完任何并发优化都是空中楼阁。单条跑通之后再尝试循环处理列表里的多个链接。这时候重点不是速度而是稳定性中途某个请求失败脚本能不能继续跑失败之后有没有记录7.2 批量任务的三件套日志、输出命名、失败重试批量任务和单条任务完全是两个量级的事。单条任务跑挂了重新执行一次就行。批量任务跑到一半挂掉你要知道之前处理到哪了、哪些成功、哪些失败否则重跑就要全量开始。批量任务最少要准备三样东西。第一日志。不要只靠 print。把请求时间、目标 URL、状态码、异常信息写到日志文件里。这样即使脚本半夜挂掉第二天也能从日志里定位问题。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s %(levelname)s %(message)s, filenamecrawler.log, ) logging.info(start request: %s, url) logging.warning(status code: %s, resp.status_code)第二输出命名。如果每个任务都要保存文件文件命名必须包含唯一标识比如数据 ID、页码、时间戳。否则多个任务写同一个文件数据会互相覆盖。第三失败重试。网络请求经常出现瞬时错误。合理的做法是每条任务最多重试 2 到 3 次重试之间加延时连续失败超过次数就记录错误并跳过不要无限重试否则脚本会被卡死在某个坏链接上。7.3 一个公开数据项目的完整流程假设需求是抓取一个公开新闻网站的列表页和详情页提取标题、发布时间和正文。整个流程可以拆成六步第一步用浏览器开发者工具确认数据来源。看看列表数据是接口返回的还是 HTML 直接渲染的。如果是接口直接记录接口 URL 和参数。第二步写一个最小请求脚本访问一个列表页打印 response 内容确认能拿到数据。第三步写解析逻辑把列表页的标题和详情页链接提取出来存成 Python 里的列表。第四步写详情页解析函数接收一个详情页 URL返回标题、发布时间、正文。先验证单条详情页能不能解析成功。第五步把列表循环和详情页循环串起来加日志、加延时、加失败重试。第六步把结果保存为 CSV 或 JSON。保存时用 utf-8 编码注意 ensure_asciiFalse避免中文变成 \uXXXX 转义字符。import csv with open(news.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title, date, content]) writer.writeheader() writer.writerows(rows)encoding 用 utf-8-sig 而不是 utf-8原因是 Excel 打开 csv 文件时utf-8 无 BOM 格式容易乱码。这个细节很多人踩过坑。7.4 项目验收的判断标准一个爬虫项目做到什么程度算完成我认为至少满足四条单条和批量都能稳定跑没有明显卡死和崩溃。数据输出完整字段没有缺失格式统一。中间失败不会整体重来日志能定位到哪条失败。请求频率合理不会给目标服务器造成压力。做到这四条“能爬”就已经升级成“能稳定跑”。剩下再考虑速度优化、并发调整、部署调度都有清晰的基础。8. 常见报错排查链路先从日志和外层因素查起8.1 报错先看现象再分层排查爬虫报错不外乎几类现象请求超时、状态码异常、解析不到数据、中文乱码、脚本崩溃。每个现象对应的排查方向都不一样。我自己排查时通常按这个顺序走先看现象本身。报的是什么错超时还是解析返回空列表再看输入。URL 对吗参数对吗页面结构变了吗再看请求头。有没有带 UAReferer 是否需要Cookie 是否过期再看频率。连续请求间隔够不够是不是被限流了再看依赖。requests、lxml 版本是否正常虚拟环境激活了没有最后才看代码逻辑。不要一上来就怀疑代码写错了。举例如果你解析不到数据先打印 resp.status_code 和 resp.text[:500]。如果状态码是 403那不是解析问题是请求被拒绝。如果状态码是 200 但返回内容里没有目标字段可能是页面结构变了也可能是数据异步加载需要回头抓包确认。8.2 一张排查表现象优先检查项常见处理请求超时timeout 设置、网络环境、目标站点响应速度加 timeout、增加重试、降低频率403 ForbiddenUser-Agent、Referer、Cookie补全请求头确认是否缺少登录态429 Too Many Requests请求频率、并发数加延时、降低并发不要急着换代理200 但解析结果为空页面结构变化、数据异步加载回抓包确认数据来源更新选择器中文乱码响应编码、文件编码设置 resp.encoding保存时用 utf-8-sig数据库/文件写入失败字段缺失、路径权限、编码打印异常详情检查数据完整性8.3 几条少走弯路的经验第一先跑小样本。我一般会先取 3 到 5 条数据跑一遍确认流程没问题再扩大到全量。不要一开始就对着 1 万条链接开跑挂了都不知道从哪里开始。第二不要一上来就开最大并发。并发数从 1 开始稳定了再加。看起来慢实际是最快的路径。第三解析逻辑单独测试。解析函数写好后单独用一个已知 HTML 片段验证输出。解析没问题再接入到请求链路里。第四重视输入清洗。爬到的数据里可能带空格、换行、特殊字符、 HTML 标签。保存之前统一清洗一次避免下游统计时报错。第五养成记录日志的习惯。print 适合学习阶段但在正式脚本里print 内容一多就分不清输出和日志。logging 从第一天开始用后面维护会轻松很多。最后再回到开头那句话爬虫真正要学的不是怎么破解别人的防护而是怎么用合理、稳定、可维护的方式获取公开数据。把请求、解析、日志、重试、频率控制这些基本功练扎实你会发现自己能解决的问题远不止一个新闻页面的标题。遇到真正拿不下的数据源先确认有没有官方 API再看服务条款然后决定是等待、降频还是放弃。这种工程判断力才是从一个写爬虫脚本的人变成一个做数据采集工程的人最关键的转变。