ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python爬虫项目实战:从requests请求到SQLAlchemy存储的完整链路

Python爬虫项目实战:从requests请求到SQLAlchemy存储的完整链路 做爬虫开发这些年我攒了不少学习笔记。最近重新翻看发现围绕爬虫项目的功能学习很多零散的知识点其实可以串成一条完整的链路——从requests发请求、selenium处理动态页面到用sqlalchemy把数据落库再到分布式爬虫和可视化面板。这篇笔记就是想把这些内容系统梳理一遍把我自己踩过的坑、验证过的方案、还在用的模板都整理出来给正在学Python爬虫的朋友一份能直接跟着动手的参考。这套东西适合什么人来读呢如果你已经会用Python基础语法想进一步接触网络爬虫或者你能写简单的采集脚本但不知道该怎么处理反爬、怎么设计存储、怎么把项目做得更完整——这篇笔记应该能帮上忙。我不会堆概念尽量用实际项目里的场景来讲原理让新手看得懂让有基础的人也能找到有价值的细节。1. 爬虫项目的知识链路从一行请求到一个完整系统1.1 爬虫项目的四层架构一个合格的爬虫项目远远不止“用requests请求一个网址然后打印出来”这么简单。我自己习惯把爬虫项目拆成四个层次来理解这也是排错和设计时的思考框架。第一层是数据获取层负责把目标数据从服务器上拿下来。技术选型通常是requests、httpx这类HTTP客户端遇到浏览器渲染的页面就需要selenium或Playwright这类自动化工具。第二层是数据解析层负责从拿到的HTML、JSON等格式里提取出干净、结构化的字段。常用的手段是XPath、CSS选择器、正则表达式以及针对JSON数据的jsonpath。第三层是数据存储层负责把解析结果持久化方便后续使用和分析。我自己用得最多的是SQLAlchemy加上MySQL搭配Redis做去重和队列。第四层是调度与监控层负责管理多个任务、多个节点、失败重试和运行情况的可视化这是项目走向工程化的分水岭。打个比方整个流程就像去图书馆查资料获取数据是找到书架解析是逐页摘抄存储是把摘抄卡片归档到抽屉调度和监控则是在管理“今天查哪几本书、谁去查、查得怎么样了”。很多人学爬虫卡在第一步以为拿到HTML就等于爬虫学完了其实后面的解析、存储和调度同样重要甚至更考验综合能力。1.2 我常用的技术选型组合针对不同场景我会用不同的组合这里分享一套我自己反复验证过的选型逻辑数据场景技术组合理由静态网页、翻页列表requests lxml/BeautifulSoup轻量、速度快、写起来简单动态渲染页面点击加载、滚动加载selenium / Playwright直接操作浏览器能看到完整DOMApp接口、返回JSON的Web接口requests jsonpath省去解析HTML的麻烦字段更规范多站点、大规模采集Scrapy 分布式Redis框架自带调度、去重、并发和扩展机制需要给非技术人员看数据Flask ECharts / Grafana用可视化面板展示采集结果这套选型的核心思路是能用轻量方案就不要上重型工具。很多人一上来就学Scrapy遇到一个小也要用框架或者一看到页面复杂就上selenium忽略了也许直接调用底层接口就能拿到数据。正确的做法是先分析页面和数据来源再用最省成本的方案实现。2. 请求模块的原理与进阶requests和它背后的HTTP常识2.1 requests的请求流程与关键参数requests是我几乎每天都在用的库它的设计确实非常人性化。一个最简单的请求只需要几行代码import requests url https://example.com/api/data headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/ } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(resp.text)看起来简单但有几个参数值得展开讲讲。headers是请求头它能告诉服务器“我是谁、我从哪里来、我想要什么”。服务器经常用请求头来判断请求是否来自真实的浏览器所以headers的完整性非常关键。单独的User-Agent并不够很多网站还会校验Referer、Origin、Accept等字段我建议直接复制浏览器里完整的请求头。timeout这个参数也不要忽略。没有超时设置的请求遇到服务器迟迟不响应时进程会一直卡在那里整个爬虫都会阻塞。我说的超时是连接超时和读取超时组合timeout10代表两者都是10秒。params参数则用于拼接URL查询字符串requests会自动帮你做URL编码用起来比手拼字符串舒服得多。响应对象里status_code表示HTTP状态码text是文本内容json()方法可以直接把JSON格式的响应解析成Python字典。如果目标接口返回的是JSONresp.json()是高频使用的一行代码。2.2 提升请求稳定性的几个技巧第一个技巧是使用Session对象。Session会保存请求之间的cookie并且复用底层的TCP连接让多次请求的速度明显提升。登录状态的模拟、连续翻页的会话保持都离不开Sessionsession requests.Session() session.headers.update(headers) session.get(https://example.com/login) data session.get(https://example.com/profile).json()第二个技巧是设置重试机制。requests默认不重试网络抖动或者服务器临时返回5xx错误时程序会直接抛异常。我一般会通过urllib3的Retry配合HTTPAdapter来实现from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry session requests.Session() retry Retry( total3, # 最多重试3次 connect3, # 连接失败重试3次 backoff_factor1, # 重试间隔 1s、2s、4s 递增 status_forcelist[500, 502, 503, 504] ) adapter HTTPAdapter(max_retriesretry) session.mount(http://, adapter) session.mount(https://, adapter)backoff_factor让重试间隔逐步拉长避免在服务器还没恢复时反复请求加剧对方压力。还有一个细节status_code为200不代表数据正确很多接口会把业务错误放在JSON的code字段里比如{code: 50001, msg: 参数错误}。脚本里一定要检验业务状态码不能只看HTTP状态码。第三个技巧是控制请求频率。爬虫新手最容易犯的错就是在一个循环里无脑快速请求结果触发对方封禁IP。我习惯在两次请求之间加一点随机延时尤其避免固定间隔的请求模式。3. 反爬虫识别和应对思路的安全边界3.1 常见反爬机制的识别特征做爬虫一定要懂反爬不然寸步难行。但首先要明确一个边界学习反爬的目的是理解网站的安全机制确保自己的合规采集而不是去攻击或破坏别人的系统。我下面讲的识别方法都是用于判断“为什么我的请求被拦了、应该怎么调整自己的代码策略”。反爬手段典型表现识别与应对思路请求头校验返回403、418对比浏览器请求头补齐UA、Referer、Accept-Language等频率限制请求几次后就封IP或弹验证码降低频率、随机延时、使用代理池字体反爬页面文字显示正常但源码是乱码下载页面字体文件解析映射关系数据加密签名接口需要携带加密sign参数分析JS生成逻辑或用自动化浏览器执行动态渲染HTML源码里没有数据数据是JS加载的抓接口或改用selenium验证码出现图形验证码、滑块接入打码平台或设计流程规避频繁验证遇到403时我习惯先看响应头里的Server字段、页面的标题和返回体。如果是Cloudflare这类防护体系需要先判断它拦截的层级但更建议优先考虑合规途径比如查询对方开放的API、协商合作或者改用公开的数据源。绕过强防护破解不仅费劲还有法律风险。3.2 selenium什么时候才需要上场有些数据是页面加载后通过ajax请求渲染出来的用requests直接请求HTML源码结果什么都没有。这时候有两条路一条是抓包找到后台接口直接请求JSON数据另一条是用selenium启动真实浏览器让页面自动加载完再取内容。我大部分时候优先走接口路线。用浏览器开发者工具打开Network面板刷新页面找到返回数据的那条XHR请求分析它的参数和返回结构然后用requests直接请求。这样重量轻、速度快也不容易被识别为自动化工具。只有当接口请求的过程特别复杂比如需要大量JS计算签名、加密参数或者数据藏在webpack打包的代码里难以分析时我才会上selenium。如果真的要用selenium有几条经验供参考。第一建议用无头模式加防检测参数启动减少资源占用from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsoptions) driver.get(https://example.com) html driver.page_source driver.quit()第二webdriver的特征非常明显。正常浏览器会有一些自动化测试相关的全局变量--disable-blink-featuresAutomationControlled可以隐藏一部分特征但不是万能的。第三selenium的运行速度远慢于requests一个页面动辄几秒钟采集量大时非常痛苦。我的建议是把requests当作主力selenium作为攻坚手段两者结合才能平衡效率和成功率。4. 解析与字段提取把网页变成干净数据4.1 三类解析方式的选择拿到HTML或者JSON后下一步是提取字段。解析方式我分成三类来选。正则表达式适合从一段文本里抓固定模式的内容比如提取所有链接、手机号、日期。它的优点是性能高缺点是写起来容易出错HTML结构一变就要重写。XPath是我在解析HTML时的第一选择。它通过路径表达式定位节点配合lxml解析库速度快、写法直观。几个高频表达式目的XPath表达式选取所有class为title的节点//*[classtitle]选取第一个div下的所有a标签//div[1]//a按文本内容匹配//span[contains(text(), 关键词)]取节点属性值//img/src取节点文本//h1/text()BeautifulSoup的语法对新手更友好不需要记太多表达式用find和find_all配合属性就能解析适合小项目和快速原型。大型项目中我会倾向lxmlXPath因为性能和可维护性更好。jsonpath则用于JSON数据的取值。接口返回的嵌套数据结构非常深用data[result][list][0][title]这种写法遇到层级稍深就非常啰嗦jsonpath可以用类似查询表达式的语法直接定位from jsonpath import jsonpath data { code: 0, data: { list: [ {title: 爬虫学习笔记, views: 1000}, {title: 数据存储实践, views: 800} ] } } titles jsonpath(data, $.data.list[*].title) print(titles) # [爬虫学习笔记, 数据存储实践]4.2 接口爬虫与页面爬虫的取舍在爬虫功能学习里辨析“接口爬虫”和“页面爬虫”是很关键的一课。页面爬虫直接请求网页拿到HTML再解析实现起来直观缺点是数据往往分散在复杂的标签里而且页面结构一改就失效。接口爬虫直接请求网页背后那个返回数据的接口通常返回JSON结构清晰、字段完整。怎么发现接口用浏览器开发者工具切换到Network面板刷新页面重点关注XHR或Fetch类型的请求记录。逐个点击查看响应数据找到包含目标内容的那个请求。它的URL、请求方法、参数、请求头就是这个页面背后的“数据源”。很多异步加载页面用这种思路就能轻松解决。比如某些电商评论页滚动后出现更多评论实际是发送了一个带page参数的POST请求。直接构造这个请求比等浏览器滚动再抓取要快一个数量级。接口爬虫有个前置条件需要分析出参数规律、必要的签名算法等。分析不出来的时候再用selenium作为兜底。两条路线都要会这是爬虫开发的基本功。5. 数据存储SQLAlchemy连接采集与持久化5.1 SQLAlchemy的核心价值数据被解析出来之后如果只打印在终端里那就什么用都没有。我第一次做爬虫项目时就吃了这个亏辛辛苦苦爬了几万条数据一段代码异常退出全部丢失只能重跑。从那以后我养成了“边爬边存”的习惯存储层成了整个项目最不能省的一环。SQLAlchemy是Python生态里最常用的ORM对象关系映射库。它的核心价值在于你可以用Python类来定义数据表的结构用操作对象的方式来读写数据库不需要手写大量SQL语句。对于爬虫项目来说这意味着代码更清晰、可维护性更高同时它内置了连接池能自动管理连接不会因为频繁请求数据库而耗尽连接数。5.2 一个可以直接用的存储示例下面是我在爬虫项目里常用的模板化写法以MySQL连接为例from sqlalchemy import create_engine, Column, String, Integer, DateTime from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime Base declarative_base() class Article(Base): __tablename__ articles id Column(Integer, primary_keyTrue, autoincrementTrue) title Column(String(200)) url Column(String(500), uniqueTrue) source Column(String(50)) create_time Column(DateTime, defaultdatetime.now) engine create_engine( mysqlpymysql://root:passwordlocalhost:3306/spider_db?charsetutf8mb4, echoFalse ) Base.metadata.create_all(engine) Session sessionmaker(bindengine)写入数据时我用session.add()和session.commit()组合with Session() as session: article Article(title爬虫学习笔记, urlhttps://example.com/1, sourceexample) session.add(article) session.commit()这里有两个实操经验。第一去重不能只靠应用层判断要依靠数据库的唯一约束。我在url字段上加了uniqueTrue重复插入会抛异常再用try捕获异常判断数据是否已经存在这样即使多个爬虫节点同时运行也不会插入重复数据。第二批量插入用bulk_save_objects或add_all。一条条commit在数据量大时性能惨不忍睹改成批量提交一次性写入速度能提升几十倍。如果数据需要更新而不是插入比如评论数、价格这类字段每天变化我习惯先用session.query按唯一键查一下存在就更新不存在就新增。这种“更新优先、插入兜底”的策略更贴近真实业务场景。5.3 存储方案的横向对比不同数据形态存储方案的选择也不一样。我总结过一张选型表存储方案适合的场景优势不足MySQL SQLAlchemy结构化数据、关系明确、需要统计查询事务可靠、查询灵活表结构固定变更成本高Redis去重指纹、请求队列、临时计数器性能极高、支持过期不适合复杂查询和持久化MongoDB字段不固定的嵌套数据无schema、写入快事务和复杂关联弱CSV / Excel临时交付给非技术人员人可直接打开数据量大时打开慢、无法并发举例来说爬取京东评论这种字段相对固定的数据我用MySQL做分布式爬虫的去重和任务队列我用Redis爬取页面上的半结构化内容比如不同来源的新闻信息字段随时可能增加我会考虑MongoDB。没有最好的存储只有当前场景下最合适的存储。6. 分布式爬虫和可视化学习项目走向工程化6.1 分布式爬虫到底在解决什么问题单机爬虫的瓶颈很明显一台机器的带宽、CPU、内存、IP资源都有限碰到成百上千万的数据规模就跑不动了。分布式爬虫的思路是把任务分给多台机器上的多个爬虫节点同时执行每个节点处理一部分任务最终汇总到同一个数据存储里。理解分布式爬虫核心是弄懂三样东西任务队列、去重集合、调度器。任务队列负责存放待抓取的URL多个节点从中取任务。去重集合记录已经处理过的URL确保同一个地址不会被两个节点重复抓取。调度器负责协调整个流程决定哪个请求由哪个节点执行、什么时候执行。Scrapy结合Redis是最常见的Python分布式爬虫方案。Scrapy自身是单机框架通过scrapy-redis将调度器和去重组件替换为Redis后端就可以实现多机协同。每个节点的爬虫从Redis队列里读取URL抓到的数据各自写入公共的存储整个系统就像多个员工同时从工单池里领活干。这里要补充一个常被混淆的概念——异步。爬虫里的异步通常指asyncio或Scrapy的异步并发机制它跟分布式是两回事。异步解决的是单机内“等待网络响应时去做别的事”分布式解决的是“多机协同干活”。一个用协程提高单机吞吐量一个用集群扩展整体规模二者可以叠加使用。初学者先把单机异步学好再考虑分布式顺序不要反了。6.2 可视化面板从哪入手爬虫跑起来之后怎么看它跑得怎么样总不能在终端里等日志一条条滚动吧。可视化面板就是干这个用的。我的做法是把采集结果写入MySQL后用Flask写一个简单的Web应用从SQLAlchemy模型里读取统计。请求量、成功失败率、入库条数、抓取趋势这些关键指标用ECharts或者Grafana画成图表整个项目就有了“仪表盘”。可视化的核心不是为了好看而是为了让项目状态可以被直观感知。一次抓取任务在哪个时间段失败了哪个栏目入库速度突然变慢通过图表一眼就能看出来。哪怕只是一个简单的曲线图也比纯日志高效得多。我推荐从Flask ECharts入手因为它对Python开发者最友好。后端提供JSON接口返回统计数据前端用ECharts渲染图表三天左右就能搭出一个够用的监控面板。之后想再扩展功能比如邮件告警、定时任务也都是围绕这套框架自然生长出来的能力。能写爬虫只是开始能把爬虫系统清晰地展示出来才是工程化的标志。7. 从热门场景看爬虫项目的设计思路7.1 游戏战绩查询类项目网上经常能看到“Python爬虫查王者战绩”这类需求。游戏战绩数据本质上来自某个JSON接口爬虫的工作就是请求这个接口、解析战绩数据、展示胜率和场次。这种项目很适合用来练习“接口爬虫”的完整套路但它也有明显的合规风险——战绩属于个人数据未经授权的采集和展示应当避免。我的建议是这类需求当作学习案例来分析是可以的从功能设计上理解它“从接口取数、用sqlalchemy入库、再用Web页面展示”的链路价值但不要真的去采集大量玩家数据或提供公开查询服务。学习爬虫的目的是掌握技术原理不是去触碰别人隐私。7.2 内容社区与电商评论的共性难点热搜词里出现的快手爬虫、抖音爬虫、小红书爬虫、京东评论爬虫、天猫爬虫都是典型的工程挑战但它们的难点其实有共性。第一个共性是签名与参数加密。短视频平台、内容社区在接口层通常会做签名校验缺少对应的加密参数请求会被拒掉或返回假数据。应对方式有两种一是从JS代码里逆向出签名算法二是用selenium或Playwright执行真实浏览器。前者技术门槛高后者性能受限。对绕过高强度风控体系的行为我一直强调要慎重避免触碰法律底线个人学习更推荐寻找开放接口或公开数据集。第二个共性是数据频率与封禁控制。电商评论这类接口对单位时间内的请求次数异常敏感触发风控后轻则弹验证码重则限制账号。我处理京东评论时会刻意把请求间隔控制在1到3秒之间的随机值并限制单账号并发数量宁可慢一点也不要断了数据源。第三个共性是合规边界。影视类爬虫涉及版权问题“红果短剧爬虫”“爬虫源影视”这类项目一定要明确用途——如果目的是做个人学习或内容检索注意不要传播下载内容如果是商业使用请务必走正规合作渠道。steam、立创商城这类以公开数据为主的平台爬取公开的销量、价格、库存信息用于学习分析相对友好但同样不能高频干扰服务。珍爱、粉笔这类包含用户个人数据和付费内容的平台切割掉不要碰。说到底这些场景考察的都是同一套基本功分析数据来源、处理反爬、设计存储、控制节奏。平台换来换去核心链路不变。遇到一个新目标我会先把数据流画清楚再决定用requests还是selenium、存MySQL还是Redis、要不要上分布式。这个分析能力才是爬虫项目学习中真正值钱的东西。8. 学习路线、高频报错与排查技巧8.1 我的爬虫学习路线建议经常有读者问我爬虫怎么学才系统。我基于自己的学习经历和带新人的经验给出一条实操性强的路线打底HTTP和HTML不用全懂但要明白请求方法、状态码、请求头、Cookie、网页结构这些基本概念。掌握requests 解析用requests请求目标网站用lxml和正则提取字段。这时候做一些单页面的小项目比如爬取自己的博客列表。接入SQLAlchemy存储把解析出的数据写入MySQL学会建表、写入、去重、查询。到这里一个完整的单机爬虫项目基本成型。处理动态页面学习浏览器开发者工具的Network抓包寻找接口必要时用selenium处理复杂的渲染页面。进阶Scrapy与分布式用Scrapy重构项目感受框架带来的调度、并发、去重能力再引入Redis实现分布式扩展。工程化收尾加日志、加可视化、加定时任务让爬虫可以被监控、可维护。这条路线走下来大概需要三个月到半年取决于个人投入程度。不建议一上来就啃Scrapy源码或者分布式架构先跑通一个完整的小项目比什么都重要。8.2 高频报错速查表我把这些年遇到的问题整理成一张速查表每个问题都是我或身边朋友真实踩过的坑报错或现象可能原因排查方向TimeoutError网络不稳定、目标服务器响应慢、没设超时增加timeout参数、设置重试机制403 / 451请求头被识别、IP被限制检查UA和Referer、降低频率返回乱码编码识别错误用resp.apparent_encoding检测编码解析列表一直为空选择器表达式不对、数据是JS渲染的用开发者工具确认节点结构、改抓接口JSON解析报错返回的不是JSON而是HTML先打印resp.text看内容Duplicate entry主键或唯一键冲突捕获异常、改用更新策略SSL证书报错目标站点证书异常考虑verifyFalse但需注意安全风险内存持续增长数据积压未入库、循环中累积对象边爬边存、及时释放变量举一个排查实例。我之前写短视频平台爬虫接口一直返回空数据状态码却是200。先打印resp.text看了一眼发现返回的是一个JS脚本而非JSON。顺着这条线分析才确认是缺少签名参数。这类问题用“先看响应内容、再判断拦截层级”的思路一般都能快速定位。还有一类问题不是报错而是数据质量问题。比如解析字段时拿到了带空白和换行的文本、时间格式不统一、金额字符串无法求和。我都会在解析层做一层清洗统一用strip()去掉空白、规范时间格式、把数字字符串转成数值。不要指望原始数据是干净的清洗是爬虫流程里的必要环节。最后分享两条个人经验踩过几次坑之后我对爬虫学习最大的体会是笔记和项目要一起成长。只记知识点不跑项目记完就忘只跑项目不总结下次遇到同样问题还是不会。我每完成一个爬虫项目都会把链路拆解出来记录哪些环节用了什么方案、为什么这么用、踩了什么坑。这篇笔记本身就是这么来的。另外一个建议是爬虫学习和平台选择有关但也不完全有关。今天的热门目标可能是短视频平台明天又可能是另一个新平台。如果你只盯着某一个平台的反爬细节平台一改版就会很被动但如果你掌握了HTTP原理、页面解析、数据存储、工程化设计这套通用能力换什么目标都能举一反三。这也是为什么我反复强调项目里的requests、selenium、sqlalchemy这些知识点最终要沉淀成你分析问题和设计系统的思维框架。最后再分享一个小技巧吧。爬虫项目的代码结构从一开始就分模块来写——请求模块、解析模块、存储模块、调度模块彼此独立接口定义清楚。哪怕是一个几百行的小项目也保持这个结构。养成这个习惯之后你会发现往项目里加新功能、修bug、换目标平台都轻松得多。等到项目变大了这种清晰的分层简直能救命。
返回列表