资讯详情 Python爬虫实战:从网页剖析到数据落库的完整路径
📅 2026/10/16 21:58:00
简介Python网络数据抓取代码是一份面向Python初学者的爬虫与数据采集实践包适合数据分析师、数据科学家、机器学习与自然语言处理方向入门者自学也可用于网站内容采集、电商信息监控、舆情监测、市场调研等典型任务。资源围绕网络请求、页面解析、文件存储三大环节组织包含requests发送请求的基础脚本、基于BeautifulSoup遍历文档并抓取豆瓣读书简介的示例、新东方头像批量下载等真实场景代码同时配有元组、列表、字典、循环、条件等语法练习文件以及自定义函数与文本/CSV读写操作便于从基础语法平滑过渡到完整爬虫项目。压缩包共21个文件以16个py脚本为核心另有2个txt、2个csv测试数据与1张jpg演示图整体仅34KB轻量易部署。目前已有4379人学习下载适合边看边跑代码快速理解网页抓取、数据解析与本地存储的完整链路建议在遵守robots协议和相关法律法规、控制请求频率的前提下使用。1. Python网络数据爬虫抓取代码先想清楚再动手Python网络数据爬虫抓取代码说白了就是写一段脚本替你把网页上的信息批量取下来。很多人拿到这个标题第一反应是去搜“爬虫代码”但真正干过这行的人都知道写代码只占整个工作量的三成剩下七成花在分析网页结构、定位数据来源、处理反爬和清洗结果上。这篇文章想解决的问题很直接给你一条从零开始能走通的路径让你知道工程序怎么写、参数怎么调、挂了之后怎么看日志而不是给你一段跑一次就废的临时脚本。适合三种人刚入门想系统接触爬虫的开发者、工作中临时要抓点公开数据的分析人员、以及看完网上一堆碎片代码仍拼不出完整流程的自学者。先说结论爬虫不是不能写但第一步永远是搞清楚数据的真实来源而不是急着敲代码。2. 写第一个爬虫前的基础环境准备、最小请求与网页定位2.1 环境准备requests、lxml 与 BeautifulSoup 怎么选爬虫的技术栈选择比大多数人想象中简单。一个能开工的 Python 环境只需要三样东西请求库、解析库、以及一个顺手的数据存储方式。请求库我建议直接用 requests它是目前公认的 HTTP 客户端首选API 设计得很人性化get、post、session 这几个核心方法覆盖了绝大多数抓取场景。解析库在 lxml 和 BeautifulSoup 之间做个选择我的习惯是解析表格、列表这类结构化数据用 lxml因为它的 XPath 语法表达能力更强、执行速度更快如果只是简单提取标题、正文BeautifulSoup 的 find 系列方法更直观但性能上会慢一些。安装命令很简单直接在终端执行pip install requests lxml beautifulsoup4这里有个容易忽略的细节如果你使用的是 Linux 系统lxml 在某些环境下需要额外依赖 libxml2 和 libxslt编译安装时可能会报错。遇到这种问题不要急着换库先执行apt install libxml2-dev libxslt1-devDebian 系或yum install libxml2-devel libxslt-develCentOS 系补上系统依赖再重新安装。Windows 和 macOS 一般没这个麻烦直接装就行。开始动手之前还要确认一件事你的 Python 版本。requests 对 Python 3.6 以上的支持已经很成熟爬虫代码里大量用到 f-string 字符串格式化和类型注解如果还在用 Python 2.x我劝你趁早换掉。这个选择不是喜好问题而是 Python 2 在字符串编码处理上有太多历史包袱中文网页抓取时会在编码上吃大亏后文避坑章我会详细展开。2.2 最小抓取代码先看数据到底长什么样环境准备好了我们来写第一段真正能跑的代码。目标很简单请求一个网页把返回的状态码和 HTML 文本打印出来。这段代码看似平平无奇但它能帮你验证三件重要的事目标网站是否允许你的客户端访问、网络链路是否通顺、页面返回的内容编码是否正常。import requests url https://example.com/news # 换成你要抓取的目标地址 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 } resp requests.get(url, headersheaders, timeout10) print(状态码:, resp.status_code) print(文本长度:, len(resp.text)) print(网页前2000字符:, resp.text[:2000])这段代码有几个关键点需要解释。headers 里的 User-Agent 非常重要很多网站对裸的 Python 请求会直接返回 403 或 418伪装成浏览器能绕过最简单的反爬拦截。timeout10 是请求超时时间单位是秒这个参数必须写否则遇到响应很慢的服务器你的脚本会无限期卡住排查起来非常痛苦。运行后看输出。状态码是 200 说明请求成功403 表示服务器拒绝访问大概率是 User-Agent 不够像浏览器500 说明目标网站自身出问题了。文本长度如果只有几百个字符那通常不是你要找的内容可能是跳转页面、登录页面或者反爬验证页面。这一步的意义在于先确认数据源能访问再谈解析。2.3 用浏览器开发者工具定位目标元素很多人拿到一个网页就开始猜 XPath 路径这是很没效率的做法。正确的工作方式是打开浏览器开发者工具用检查元素功能直接定位目标数据在 HTML 中的位置。以抓取一个资讯网站的文章标题为例你需要在元素面板里右键标题文本选择“检查”就能看到标题对应的 HTML 标签结构。定位时要重点看三个信息标签名、class 或 id 的值、以及它在 DOM 树中的层级位置。比如标题在h2 classpost-titlea href/detail/123文章标题文字/a/h2这样的结构中你的提取路径就很明确了。这里补充一个经验之谈优先用 id 定位其次用 class最后才用标签名和层级组合。id 在一个页面里是唯一的最稳定class 可能被多处复用需要结合上下文纯标签名太宽泛很容易误抓到导航菜单或其他干扰项。开发者工具里还有一处常被忽视的信息叫 Network 面板。打开后刷新页面你会看到浏览器发出的所有请求。很多网页的数据不是写死在 HTML 里的而是通过 JavaScript 异步请求接口获取的直接抓页面源码只能拿到一个空壳。在 Network 面板里按 XHR 过滤你就能看到真正的数据接口这个我会在下一章给出完整抓法。3. 把数据从网页里抠出来正则、XPath 与 CSS 选择器的选型与写法3.1 三种解析方式的适用边界解析 HTML 有三种主流手段正则表达式、XPath、CSS 选择器。新手最容易陷入的误区是以为正则万能什么内容都用正则去匹配。实际上正则适合处理不规则的文本模式比如从一段 JSON 字符串里抠出某个字段值但在解析 HTML 这种层级结构时正则写起来又长又脆一个标签嵌套就能让它失效。XPath 和 CSS 选择器才是解析 HTML 的正道。XPath 通过节点路径定位元素比如//h2[classpost-title]/a/text()表示“选择所有 class 为 post-title 的 h2 标签下的 a 标签的文本内容”表达能力极强可以处理各种复杂条件。CSS 选择器语法更简洁h2.post-title a是同样的效果适合熟悉前端的人使用。需要说明的是BeautifulSoup 本身支持这两种语法但底层实现会转成 lxml 解析所以追求性能时直接用 lxml 更方便。三种方式的选型逻辑总结如下数据藏在 JSON 字符串里用正则HTML 层级结构、属性条件多、要按复杂逻辑抽取时用 XPath结构简单、追求代码可读性时用 CSS 选择器。从工程实践看XPath 的优先级最高因为它同时具备定位、过滤、提取三种能力一套语法通吃大多数场景。3.2 用 XPath 写一套稳定抽取规则用 lxml 写 XPath 抽取逻辑代码路径非常直观。下面是一个从资讯列表页抽取标题和链接的完整例子from lxml import html # 假设 resp.text 是上一步抓到的网页源码 doc html.fromstring(resp.text) # 提取所有标题文本 titles doc.xpath(//h2[classpost-title]/a/text()) # 提取所有链接 links doc.xpath(//h2[classpost-title]/a/href) for title, link in zip(titles, links): print(title.strip(), link)代码里的两个 XPath 表达式是核心。//表示在整个文档中查找[classpost-title]是属性过滤条件/a/text()是取子节点的文本内容/a/href是取子节点的 href 属性值。这里有几个容易踩的细节text() 可能返回包含前后空白的字符串所以后面加了 strip() 清理href 取到的可能是相对路径比如 /detail/123 而不是完整的 http 链接落到数据库之前需要拼接成绝对地址。写这套规则时我的建议是先在浏览器开发者工具里把目标元素的结构截图或抄下来然后在本地跑一个简单的测试脚本打印 XPath 匹配结果。不要相信一次写对XPath 调试基本靠试错每改一次表达式就重新运行一次直到输出结果和页面上看到的一致。这个循环很枯燥但它是唯一可靠的办法。3.3 数据在接口里动态加载页面的抓法前面提到很多页面数据来自异步接口。判断依据很简单用 requests 请求到的 HTML 源码里没有目标内容但浏览器里能看到。这种页面直接解析 HTML 会一无所获正解是找到那个真正的数据接口。在开发者工具的 Network 面板里刷新页面把类型过滤到 XHR你会看到一串请求。逐个点击查看响应内容找到返回 JSON 数据的那个请求它的 URL 往往长这样https://example.com/api/list?page1size20。浏览器地址栏输入这个 URL 也能看到返回的 JSON说明接口没有额外的鉴权要求那就可以直接在爬虫里请求这个地址import requests import json api_url https://example.com/api/list params { page: 1, size: 20 } headers {User-Agent: Mozilla/5.0} # 这里可以简化 resp requests.get(api_url, paramsparams, headersheaders, timeout10) data resp.json() # 直接解析 JSON for item in data.get(data, []): print(item.get(title), item.get(url))这段代码的思路和抓 HTML 完全不同不再关心页面长什么样而是直接消费后端接口的数据。params 参数是 URL 查询参数requests 会自动编码拼接到 URL 后面。响应用 resp.json() 直接解析省去了正则抠数据的麻烦。很多动态页面的数据接口都能用这个思路拿下效率比模拟浏览器渲染高出一个数量级。3.4 一次完整的抓取脚本请求、解析、保存把上面的零散步骤串起来就是一个能直接跑的最小完整脚本。下面这段代码覆盖了请求、解析、保存三条链路抓取某资讯网站前两页的文章标题和链接写入 CSV 文件import requests from lxml import html import csv def fetch_page(page_no): url https://example.com/news/list params {page: page_no} headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} resp requests.get(url, paramsparams, headersheaders, timeout10) resp.encoding utf-8 # 显式声明编码 return html.fromstring(resp.text) def parse_page(doc): titles doc.xpath(//h2[classpost-title]/a/text()) links doc.xpath(//h2[classpost-title]/a/href) return zip(titles, links) # 抓取前两页 with open(news.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([标题, 链接]) for page in range(1, 3): doc fetch_page(page) for title, link in parse_page(doc): writer.writerow([title.strip(), link]) print(f第 {page} 页完成已写入)这里有两个参数容易被忽略。resp.encoding utf-8 是强制指定响应编码很多网页的响应头没有声明 charsetrequests 会猜错导致中文乱码显式指定可以绕开这个问题。CSV 写入时用了encodingutf-8-sig这个编码会在文件开头写入 BOM 头Excel 打开时才能正确识别中文否则你会看到一列乱码或者挤在一起的中文文本。这两处设定都是实战里换来的教训一开始就写对能省掉很多返工。4. 爬虫避坑五个高频翻车现场与排查路径4.1 解析结果为空但代码没报错现象脚本正常运行状态码 200但解析列表是空的打印出来什么都没有。这是爬虫新手最容易懵的场景没有报错反而更让人抓狂。原因分两种一是 XPath 写错了路径和网页实际结构对不上二是页面数据是异步加载的你用 requests 拿到的只是空壳 HTML里面根本没有目标内容。解决先确认属于哪种。把 resp.text 存下来复制到浏览器里打开看里面有没有你要的数据。有说明 XPath 写错了回到开发者工具里重新核对标签结构和属性值。没有说明是动态页面改用上一章的接口抓取方案去 Network 面板里找真正的数据请求。4.2 中文乱码编码不一致的两种情况现象打印出来的中文是乱码或者写入 CSV 后用 Excel 打开看到一堆看不懂的符号。原因两层编码问题叠加。第一层是 requests 猜测解码错误——响应头里没有 charset 字段时requests 会按 ISO-8859-1 解码中文自然全乱。第二层是文件写入编码不对——CSV 文件用 UTF-8 写入但 Excel 默认用 GBK 打开字符集对不上。解决请求阶段用resp.encoding resp.apparent_encoding或者直接显式指定resp.encoding utf-8看站点实际用什么编码很多国内网站在 GBK 和 UTF-8 之间摇摆。写入阶段推荐encodingutf-8-sig带 BOM 的 UTF-8 对 Excel 兼容性最好。乱码问题在爬虫里属于玄学问题重灾区但底层逻辑就这两招。4.3 请求太快被封现象、原因与应对现象脚本跑着跑着突然连续返回 403 或 429再往后可能整个 IP 都被临时限制。原因请求频率太高。网站有访问控制机制正常情况下人类浏览频率大约每 3 到 5 秒一个页面而爬虫脚本是毫秒级的连续几十个请求后必然触发风控。解决在请求之间加随机延迟最简单可靠的做法是time.sleep(random.uniform(2, 5))让间隔更像真实用户。同时给请求头补全浏览器特征Cookie 也带上能降低被识别为脚本的概率。如果目标网站明确限制了请求频率必须遵守它的规则不要尝试绕过。数据抓取是细水长流不是一次性爆破。4.4 网站改版导致旧脚本静默失效现象上周还能正常抓取的脚本这周跑完全程没有报错但输出的数据明显不对比如标题列表变短了、链接格式变了。原因网站前端结构调整class 名改了、标签层级变了、接口参数变了。爬虫的本质依赖页面结构结构一变你的 XPath 和解析逻辑就全部失效。解决解析代码要设计得足够健壮。比如 XPath 不要写得太绝对优先用稳定的属性值定位不要依赖多层嵌套路径。每次正式抓取前先跑一个小样本校验比如抓一页数据检查标题列表数量和内容是否符合预期确认无误后再全量跑。脚本里也可以加断言解析结果为空时主动抛异常而不是静默通过。4.5 合规红线遵守规则、控制频率、只抓公开数据现象抓取过程中遇到登录才能查看的内容或者遇到防爬页面无法跳过。原因目标网站对内容访问有权限控制或者设置了反爬机制你的脚本越过了允许访问的范围。解决爬虫本身是中性工具但使用有边界。我个人的操作准则如下只抓公开可见的数据不碰需要登录或有明确访问限制的内容严格遵守目标网站的 robots.txt 协议抓取前先查看其内容控制请求频率确保不影响目标网站的正常运行抓取后的数据只用于个人学习或合法用途不批量传播、不涉及隐私信息。遇到登录墙或验证码直接跳过而不是想办法绕过。数据合规问题不是后端知识点而是每个写爬虫的人必须自律的底线。5. 多线程提速与落库把脚本从能跑变成好用5.1 线程池的正确打开方式抓取量一大单线程的循环等待就成了瓶颈。每一次请求都是网络 IO大部分时间花在等服务器响应上这时候用多线程能显著提速。Python 的 concurrent.futures 模块提供了线程池接口用起来很简单但要控制并发数不要无脑开几十个线程。from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_page(page_no): 抓取单页并返回解析后的数据列表 url fhttps://example.com/news/list?page{page_no} headers {User-Agent: Mozilla/5.0} resp requests.get(url, headersheaders, timeout10) doc html.fromstring(resp.text) # 假设 parse 函数返回 [(标题, 链接), ...] return page_no, parse(doc) results [] with ThreadPoolExecutor(max_workers4) as pool: future_map {pool.submit(fetch_page, i): i for i in range(1, 11)} for future in as_completed(future_map): page_no, data future.result() results.extend(data) print(f第 {page_no} 页完成)max_workers 是线程数量建议控制在 4 到 8 之间。太多线程会触发目标网站的风控太少又达不到提速效果。核心逻辑是网络 IO 密集型的任务线程池收益明显但 CPU 密集型的解析任务提升有限。如果目标网站响应很慢适当减少线程数反而更稳定。as_completed 按完成顺序处理结果比按提交顺序处理更高效。5.2 CSV 落库与字段清洗抓到数据只是第一步把数据规整地存下来同等重要。CSV 是最友好的存储格式没有数据库依赖Excel 和 pandas 都能直接读取。落库前要做字段清洗这一步能显著提升数据质量。import csv import re def clean_title(title): 清理标题中的空白字符和无关符号 title re.sub(r\s, , title) # 多个空白合并为一个 return title.strip() with open(news.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([标题, 链接, 来源]) for title, link in raw_results: clean_link link if link.startswith(http) else https://example.com link writer.writerow([clean_title(title), clean_link, 某资讯网站])清洗规则根据目标数据的特征定制。常见的有去掉首尾空白、合并内部多余空格、移除 HTML 标签残留、链接补全为绝对地址、表格字段去重。这一步会在后续做数据分析时省下大量时间。CSV 编码用 utf-8-sig 已经是老生常谈但每次都值得强调因为乱码问题永远是文件格式踩坑的第一名。5.3 验证抓取质量三个低成本习惯抓取完毕数据到底准不准需要一套成本很低的验证方法。第一个习惯是数量核对从目标网站页面上数一下实际条目数再对比抓到的数据量数量对不上说明有漏抓或重复抓。第二个习惯是抽样人工核对随机抽 5 到 10 条数据在浏览器里打开链接看内容是否一致抽样能发现字段映射错位的问题。第三个习惯是键值去重用 Python 的集合对链接或标题去重重复率过高说明爬虫页面有重叠或是翻页逻辑有 bug。这三个习惯加起来不超过三分钟但能拦截住绝大多数数据质量问题。我在实际开发中见过太多看起来能跑、实则数据错乱的脚本所有问题都是靠这几个小习惯发现的。数据质量问题是爬虫工程里的隐性成本晚发现一天后续分析和建模都要跟着返工。这几条验证路径是我用血泪经验换来的做项目时一定要纳入常规流程。写爬虫这件事越往后越体会到工程思维的重要性先调研目标结构再写代码跑通后加健壮性处理最后做数据质量验证。每个环节都有各自的细节和坑但只要按这条路径走绝大多数问题都能在早期被拦住。我自己刚开始接触爬虫时也写过拿着正则硬抠 JSON 的笨代码、被编码问题折腾到半夜的经历一路踩过来才整理出这套相对稳妥的做法。希望帮到你。本文还有配套的精品资源点击获取