简介: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-dev(Debian 系)或yum install libxml2-devel libxslt-devel(CentOS 系)补上系统依赖,再重新安装。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, headers=headers, timeout=10) print("状态码:", resp.status_code) print("文本长度:", len(resp.text)) print("网页前2000字符:", resp.text[:2000])这段代码有几个关键点需要解释。headers 里的 User-Agent 非常重要,很多网站对裸的 Python 请求会直接返回 403 或 418,伪装成浏览器能绕过最简单的反爬拦截。timeout=10 是请求超时时间,单位是秒,这个参数必须写,否则遇到响应很慢的服务器,你的脚本会无限期卡住,排查起来非常痛苦。
运行后看输出。状态码是 200 说明请求成功;403 表示服务器拒绝访问,大概率是 User-Agent 不够像浏览器;500 说明目标网站自身出问题了。文本长度如果只有几百个字符,那通常不是你要找的内容,可能是跳转页面、登录页面或者反爬验证页面。这一步的意义在于:先确认数据源能访问,再谈解析。
2.3 用浏览器开发者工具定位目标元素
很多人拿到一个网页就开始猜 XPath 路径,这是很没效率的做法。正确的工作方式是打开浏览器开发者工具,用检查元素功能直接定位目标数据在 HTML 中的位置。以抓取一个资讯网站的文章标题为例,你需要在元素面板里右键标题文本,选择“检查”,就能看到标题对应的 HTML 标签结构。
定位时要重点看三个信息:标签名、class 或 id 的值、以及它在 DOM 树中的层级位置。比如标题在<h2 class="post-title"><a 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[@class="post-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[@class="post-title"]/a/text()') # 提取所有链接 links = doc.xpath('//h2[@class="post-title"]/a/@href') for title, link in zip(titles, links): print(title.strip(), link)代码里的两个 XPath 表达式是核心。//表示在整个文档中查找,[@class="post-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?page=1&size=20。浏览器地址栏输入这个 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, params=params, headers=headers, timeout=10) 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, params=params, headers=headers, timeout=10) resp.encoding = "utf-8" # 显式声明编码 return html.fromstring(resp.text) def parse_page(doc): titles = doc.xpath('//h2[@class="post-title"]/a/text()') links = doc.xpath('//h2[@class="post-title"]/a/@href') return zip(titles, links) # 抓取前两页 with open("news.csv", "w", newline="", encoding="utf-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" 是强制指定响应编码,很多网页的响应头没有声明 charset,requests 会猜错导致中文乱码,显式指定可以绕开这个问题。CSV 写入时用了encoding="utf-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 之间摇摆。写入阶段推荐encoding="utf-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 = f"https://example.com/news/list?page={page_no}" headers = {"User-Agent": "Mozilla/5.0"} resp = requests.get(url, headers=headers, timeout=10) doc = html.fromstring(resp.text) # 假设 parse 函数返回 [(标题, 链接), ...] return page_no, parse(doc) results = [] with ThreadPoolExecutor(max_workers=4) 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="", encoding="utf-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 的笨代码、被编码问题折腾到半夜的经历,一路踩过来才整理出这套相对稳妥的做法。希望帮到你。
本文还有配套的精品资源,点击获取