
说实话我自己最开始学爬虫的时候就是被一堆术语搞懵了requests、Selenium、Scrapy、反爬、异步、分布式……每个词单独看都能懂合在一起就不知道它们到底在项目里扮演什么角色。后来把几个爬虫项目完整做下来回头再看才发现所谓“爬虫项目”其实就干四件事把网页数据拿下来、把有用信息摘出来、把数据存好、再把整个过程管好。这篇学习笔记就是围绕爬虫项目的功能学习展开的适合刚入门想搭一个完整爬虫的人也适合那些已经会写单脚本但总感觉缺了工程化的朋友。我会按照自己实际做项目时的顺序从最基础的请求讲到数据存储、动态页面处理再聊到分布式和可视化这些进阶功能最后把踩过的坑一起整理出来。1. 爬虫到底在学什么先把项目功能地图画清楚1.1 核心功能拆解爬虫项目不只是“抓网页”很多人以为爬虫就是“发个请求拿到HTML用正则匹配一下”其实这只是冰山一角。一个完整的爬虫项目至少要包含四层功能请求层负责把目标网站的数据“取回来”。最简单的用requests复杂一点要处理Cookie、Headers、签名参数再复杂就要上Selenium这类浏览器自动化工具。解析层把拿到的HTML、JSON或者二进制流变成结构化数据。常用工具有正则、XPath、BeautifulSoup、pyquery。存储层把清洗后的数据写到文件、MySQL、MongoDB或者通过SQLAlchemy这样的ORM框架入库。调度与监控层负责任务分配、断点续爬、日志记录、失败重试甚至用可视化界面展示爬虫状态。我自己刚学的时候总是急于写解析代码忽略了后面两层。结果脚本跑一次没问题跑十次就崩溃数据重复、字段缺失、网站一改版就全废。后来才明白爬虫项目的难点不在于“能不能爬到”而在于“能不能稳定地、持续地、规范地爬到并存储”这才是功能学习的核心。1.2 一条爬虫请求的生命周期从URL到数据行理解爬虫功能最好的方法是跟着一条请求走完全程。假设我们要爬一个商品列表页构造URL和请求头requests发起GET或POST请求。服务器返回响应可能是HTML、JSON也可能是图片二进制流。解析响应内容定位到每个商品节点提取名称、价格、链接。对提取结果做数据清洗比如去除多余空格、转换价格格式、处理缺失值。通过SQLAlchemy的ORM模型映射把每条商品数据插入MySQL或PostgreSQL。记录日志本次请求状态、解析条数、耗时、是否有异常。这个过程中每一个环节都可能出问题请求被拒绝、解析规则失效、数据库连接超时、字段长度超出限制。所以爬虫项目里的“功能学习”其实就是把这条链路上每个环节的工具用熟、把异常想全。我后来做项目会先画一张这样的流程图哪怕不写代码思路清晰了代码自然就顺了。2. 从requests到异步网页请求功能的进阶之路2.1 requests入门最基础的请求构造requests是Python爬虫最常用的HTTP库没有之一。我记着第一次用的时候真的是两行代码就能拿到数据import requests url https://example.com/api/list headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36, Accept: application/json, text/plain, */*, } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(resp.json())这里有几个容易被忽略的细节timeout一定要设置。不设置的话遇到网络波动可能卡住几分钟影响爬虫整体调度headers里的User-Agent要带上很多服务器默认拒绝没有UA的请求resp.json()只适用于JSON接口如果是HTML就要用resp.text然后交给解析器。陆续学着学着你会发现requests就像一个“快递员”你把收件地址URL、收件人偏好headers、包裹内容data交给它它帮你跑腿。但快递员也有脾气如果目标服务器限流、封IP、要求登录requests就搞不定了这时候需要更高级的武器。2.2 异步爬虫为什么能快那么多同步requests爬虫是“一个一个来”发一个请求等服务器返回再发下一个。假如每个请求耗时0.5秒爬1000个页面就要500秒。但用异步协程比如aiohttp可以在等待响应的同时去发起其他请求相当于同时有几十个请求在空中飞总时间能缩短到十几秒。我记得第一次用异步爬虫时代码比同步难理解不少核心大概是这样的import asyncio import aiohttp async def fetch(session, url): async with session.get(url) as resp: return await resp.text() async def main(): async with aiohttp.ClientSession() as session: tasks [fetch(session, fhttps://example.com/page/{i}) for i in range(20)] results await asyncio.gather(*tasks) print(len(results)) asyncio.run(main())注意异步不是银弹。如果目标网站对并发有限制100个并发请求可能会把服务器打挂也可能让IP被秒封。所以异步爬虫一定要配合限速用一个信号量控制并发数比如每100毫秒一个请求。实际做的时候我会先用10个并发测试再逐步调大找到既不封号又足够快的值。2.3 请求头的伪装与常见反爬应对我最初天真地以为换了User-Agent就万事大吉结果还是被弹验证码。后来发现服务器会看很多维度Referer、Accept-Language、请求频率、Cookie的一致性、浏览器指纹。所以做功能学习时一定要建立“请求头完整性”的意识。一个标准的浏览器请求头至少要包含User-Agent Accept Accept-Language Accept-Encoding Referer Connection遇到最简单的反爬比如检测UA是不是爬虫只需要把上面这些补全。遇到频率限制就加延时或用随机sleepimport time import random time.sleep(random.uniform(1, 3))遇到需要登录的页面得先模拟登录拿到Cookie再带着Cookie去访问。更复杂的反爬会校验TLS指纹、JS生成动态参数那就得进入自动化工具或者逆向分析的领域。这部分内容在后面的章节细说这里先记住一个原则反爬的对抗都是围绕“让你看起来像一个真人”展开的学习功能时别老想着硬刚先想想怎么让自己的请求更“像人”。3. 数据解析与存储从HTML到数据库的完整链路3.1 解析库选型正则、XPath、CSS选择器怎么选拿到HTML之后就要从里面“挖”数据。正则表达式是万金油但写起来容易出错XPath适合复杂层级结构CSS选择器语法简洁BeautifulSoup则是对新手最友好因为容错率高、API直观。我个人在项目里的选型规则是JSON接口返回的数据直接resp.json()不需要解析库。简单的HTML取一个值用正则最快捷比如取标题title(.*?)/title。复杂列表页、详情页用XPath或CSS选择器。XPath可以通过浏览器开发者工具右键“Copy XPath”直接得到但是经常不够严谨建议学一下语法。举个例子用lxml解析一个商品列表from lxml import html doc html.fromstring(page_source) items doc.xpath(//div[classproduct-item]) for item in items: name item.xpath(.//a[classtitle]/text())[0].strip() price item.xpath(.//span[classprice]/text()) price price[0].replace(,, ) if price else 0这里有个坑xpath的索引从1开始写[1]是第一个元素不是第二个。另外xpath返回的是一个列表即使只有一个结果也是列表取之前一定要判断是否为空不然IndexError够你调试半小时。3.2 SQLAlchemy存储让爬取数据结构化落地爬虫数据存到哪里最简单的是CSV但是功能完善的项目一定会用数据库。SQLAlchemy是Python中最常用的ORM框架它让你用Python类去描述一张表然后通过session提交数据。我习惯先定义一个模型比如商品表from sqlalchemy import Column, Integer, String, Float, DateTime, create_engine from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime Base declarative_base() class Product(Base): __tablename__ products id Column(Integer, primary_keyTrue, autoincrementTrue) name Column(String(200), nullableFalse) price Column(Float, default0) url Column(String(500), uniqueTrue) created_at Column(DateTime, defaultdatetime.now)然后初始化连接和创建表engine create_engine(mysqlpymysql://user:passwordlocalhost/dbname?charsetutf8mb4) Base.metadata.create_all(engine) Session sessionmaker(bindengine) session Session()插入数据时我通常用批量操作session.add_all([Product(nameitem[name], priceitem[price], urlitem[url]) for item in data]) session.commit()这里必须提醒一个常见坑unique字段的重复插入会抛异常。所以入库前最好先查重或者用session.merge()。但merge在大批次插入时效率较低所以我更倾向于先查询已有URL集合再过滤掉重复数据最后批量insert。SQLAlchemy还提供了反爬虫数据存储的完整方案包括外键关联、事务回滚、连接池管理这些是进阶功能学习的重点。3.3 数据清洗与去重容易被忽略的关键步骤我见过很多新手同学解析完就直接插入数据库结果里面全是空字符串、乱码、重复项。数据清洗虽然不酷但直接影响后面所有分析。至少要做这几步去空格和换行用strip()清洗字符串。统一格式日期转成datetime数字去掉千分位逗号价格统一为浮点数。缺失值处理如果某个字段缺失是填空字符串还是跳过整条数据要提前定规则。编码统一爬到的中文文本如果页面是GBK编码而你没有正确解码就会出现乱码。requests拿到响应后可以通过resp.encoding gbk手动指定。去重最常用的是用URL作为唯一键在插入前先查一下现有URL集合。我在一个爬虫项目中踩过坑某商品页面的价格有时候会返回“面议”两个汉字我直接用float()转换导致崩溃。后来加了异常处理把无法转换的值记成None这才稳定下来。数据清洗看似琐碎但在功能学习的权重里至少占三成。4. 动态页面与自动化Selenium进阶玩法4.1 什么时候必须上Selenium有些网站的数据不是直接藏在HTML里的而是通过JavaScript动态渲染出来的。你用requests拿到的源码里根本没有数据只有一堆script标签。这时候有两条路一是抓接口找到浏览器发出的XHR请求直接用requests模拟二是上Selenium让真正的浏览器去渲染页面再读取渲染后的DOM。能抓接口就别用Selenium这是我反复验证过的经验。接口更轻量、更快、更容易解析。但有些接口做了加密参数或者接口的数据需要复杂的交互才能触发那就老老实实用Selenium。Selenium的基本使用流程是from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) driver webdriver.Chrome(optionsoptions) driver.get(https://example.com) # 等待页面加载 driver.implicitly_wait(5) # 拿到渲染后的页面源码 html driver.page_source driver.quit()headless模式是不显示浏览器窗口的适合服务器上跑。但调试的时候我建议不要开headless能亲眼看到页面状态定位问题快很多。4.2 反爬虫检测的破解思路与注意事项Selenium虽然能渲染页面但它本身也会暴露自动化特征。网站可以通过navigator.webdriver属性检测你是不是自动化控制。所以就有了各种隐藏特征的技巧常见思路包括设置options.add_experimental_option(excludeSwitches, [enable-automation])修改navigator.webdriver的值使用stealth.js或者undetected-chromedriver不过我要强调学习反爬虫技术目的是理解和对抗非法的网络破坏行为而不是教你攻击某个具体网站。在实际开发中第一原则永远是遵守网站的robots协议和法律法规。真正有价值的爬虫项目是爬取自己拥有或者有授权的数据比如自己的运营后台、开放API、公开的政府数据等。如果只是为了学习可以用一些沙箱站点比如httpbin.org、books.toscrape.com这些是专门给爬虫学习者练习的。另外有一类验证码是人机识别。简单的图片验证码可以用OCR滑块验证码需要模拟拖拽轨迹更复杂的点选验证码基本要接第三方打码平台。做功能学习时建议先避开有强验证码的目标把核心链路走通再慢慢研究验证码。4.3 自动化操作中的稳定性优化Selenium最大的毛病是“不稳定”网络慢一点、元素没加载出来、弹窗挡住点击脚本就容易挂。我总结的稳定性优化方案有这几条不要用time.sleep硬等改用WebDriverWait和expected_conditionsfrom selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, product-item)) )定位元素优先用id或css selector少用xpath因为前端微调容易让xpath失效。执行完每一步把当前页面截图保存下来。排查问题时截图比日志直观得多。发生异常时不要立刻退出而是刷新重试一次。我写了重试装饰器连续失败3次才告警。定期重启浏览器。长时间运行的Selenium浏览器会越跑越慢内存占用越来越大我一般每爬500个页面就重启一次driver。自动化这部分功能学习说到底就是“模拟真人操作”所以慢慢来比快更重要。真人的滑动、输入、点击都是有间隔的脚本里加上随机延时反而更不容易触发反爬。5. 工程化与可视化从脚本到可用的爬虫工具5.1 分布式爬虫的设计思路单机爬虫有瓶颈带宽有限、CPU有限、IP单一容易被封。当数据量到十万、百万级别时就要考虑分布式。分布式爬虫不是神秘的黑科技核心就是“把任务拆成很多小块分给多台机器同时跑然后把结果汇总起来”。常见的架构是一个中央调度器负责分配URL多个爬虫节点从队列里取任务、执行抓取、再把结果写回同一个存储。任务队列用Redis非常方便因为Redis支持列表、Set、延迟队列等结构。举个例子用LPUSH往Redis里放URL。每个爬虫节点用BRPOP阻塞式取URL。抓取完成后把解析结果写入MySQL把已抓取的URL加入一个Set用于去重。Scrapy本身就支持分布式配合scrapy-redis就是一套成熟的方案。但说实话如果你的项目还在起步阶段不要一上来就搞分布式。单机的性能优化没做好之前分布式只会让问题更复杂。我用过的经验法则是单机每天能稳定抓1万条数据就暂时不需要分布式等到单机跑不完、或者任务时效性要求太高了再升级架构。5.2 可视化界面让爬虫变成人人可用的工具以前爬虫都是写脚本跑完看命令行输出。后来要给不懂代码的同事用我就把爬虫包成了一个带界面的小工具。所谓“python爬虫可视化界面”本质上就是给爬虫套一个GUI或者Web前端让用户填一个关键词、选一个任务点一下“开始”就能看到进度条和结果表格。如果不想写复杂的Web用Flask起一个本地服务就够了。后端调用爬虫函数前端用简单的HTMLAJAX展示数据。更轻量的方案是用tkinter做桌面程序但样式比较丑。我更推荐用Streamlit写起来最短import streamlit as st keyword st.text_input(输入搜索关键词) if st.button(开始爬取): progress st.progress(0) # 模拟爬虫进度 for i in range(100): progress.progress(i 1) # 实际爬取逻辑 st.success(完成)可视化界面的核心价值在于把参数可配置化。比如把用户输入的关键词传给爬虫函数把爬虫状态实时回传把结果表格展示出来。它不改变爬虫本身的逻辑但让整个功能“可用性”大大提升。做这个功能时要把后台任务的超时、取消、日志输出都处理好不然界面总是卡住体验很差。5.3 逆向分析与加密参数进阶路上的硬骨头爬虫学习进阶到一定程度一定会遇到“爬虫逆向”。意思是目标网站的数据接口虽然是JSON但参数是加密的比如有个sign或者token每次请求都要动态生成。你直接用requests模拟服务器会返回“参数校验失败”。做逆向分析的典型步骤是打开浏览器开发者工具Network面板里找到XHR请求看它的Query String或Request Body里哪些参数是变化的。在Sources里搜索参数名定位到JavaScript生成代码。分析加密算法可能是MD5、SHA、AES也可能是自定义的混淆算法。用Python重写这个加密过程或者直接用PyExecJS、js2py执行JS代码。这里要提醒一下逆向分析是一门很吃经验的技能而不是一条路走到黑。很多网站有前端混淆、WebAssembly、加固等技术破解难度极大。在我做的真实项目中绝大多数遇到的所谓“逆向”其实就是定位到某个加盐哈希用Python的hashlib几行代码就能模拟。真正值得花心思的是分析思路先找参数入口再看它的依赖关系最后化简为Python实现。另外绝对不要用爬虫逆向去爬取涉及个人隐私或者受法律保护的数据比如社交平台私信、银行账户、付费内容。技术本身是中性的但使用技术的人要有边界感。我建议初学者在合法、公开、允许爬取的平台上练习逆向思路比如一些公开的天气API、股票行情API。6. 常见问题与排查实录6.1 爬虫被封IP怎么办这是每个爬虫开发者都会遇到的问题。封IP的表现有很多请求突然全是403、验证码跳出、返回内容为空。处理优先级一般是降低频率加随机延时让请求间隔更像真人。检查请求头完整性特别是User-Agent、Referer。使用Cookie会话把登录状态维持住不要每次新建会话。更换IP资源比如使用拨号VPS或者云服务商的弹性IP但要合规使用不要去碰违法违规的流量代理。搭建更完善的调度系统把被封的IP放入黑名单冷却一段时间再放出来。我在实际项目里还遇到过“不是IP被封而是请求头里带了不干净的Cookie”。排查方法是先清空Cookie直接访问看能不能拿到数据逐步加回Cookie。这种二分法排查效率很高。6.2 数据入库乱码/字段缺失排查入库乱码90%是编码问题。页面声明是UTF-8但你requests拿到的响应实际上可能是GBK。处理方式是在请求后立刻检查resp.encoding然后用resp.content.decode(utf-8, errorsignore)手动解码。另外MySQL表要统一使用utf8mb4字符集不然遇到特殊表情符号会直接报错。字段缺失则往往是解析规则不够健壮。比如页面改版把class名改了XPath和CSS选择器就失效。我养成了一个习惯把解析逻辑独立成一个函数并且用测试用例来验证提取结果。每次跑完爬虫后抽样对比一下看看提取出的字段数量和值是否正常。还有一个坑是页面里明明有数据但解析出来是空的那可能是数据是通过AJAX异步加载的requests拿到的源码里确实没有需要用Selenium或者找接口。6.3 内存泄漏与调度崩溃处理长期运行的爬虫内存会慢慢涨最后被系统kill掉。常见原因有三个一是Selenium的浏览器进程没有正常关闭每次循环都新建driver二是日志文件无限制变大三是数据队列没有限制积压越来越多。针对这几点我一般这样做每次用完Selenium在finally里执行driver.quit()而不是driver.close()。close只关闭当前窗口quit才释放全部资源。日志采用按天切割或者只保留最近N个文件。给内网数据库连接加上连接池回收逻辑避免连接泄漏。设置任务队列的最大长度超过阈值就暂停新任务。调度崩溃的排查思路也很直接先看日志有没有关键字比如OutOfMemoryError、Too many open files、Connection timed out。在Linux服务器上可以用dmesg看内核日志有没有被OOM killer杀掉的进程。做爬虫项目的时间越长越会觉得爬虫真正难的不是写爬虫而是让它一直稳定地跑下去。那些网上流传的“爬虫接单一年收入”之类的话题听听就好。真正靠爬虫做出价值的都是把它当成一个严谨的软件工程来做从需求分析、数据建模、调度监控到异常处理每个环节都做到位。这也是我整理这篇学习笔记的核心目的把散落的知识点串成一个能落地的项目功能地图。最后再分享一个小技巧学爬虫最好的方法不是看视频而是找一个目标网站从第一步开始一步步把数据存到数据库里。遇到一个坑就解决一个坑。等你把一个完整流程走通再回头去看那些框架和库的文档你会发现它们全都变亲切了。爬虫这条路入门不难但想走得远靠的是对每一个细节的较真。