
爬虫入门这件事网上教程一抓一大把但绝大多数都只教你怎么发请求、怎么解析网页却很少把“合规”两个字当回事。作为一个写爬虫写了七八年、也被人反爬折腾过无数次的老手我特别想说的是爬虫入门的第一课不是requests怎么用不是XPath怎么写而是搞清楚你到底有没有权利去抓这些数据。今天我打算用一篇基础实战的内容把“合规获取网络数据”这件事从头到尾拆一遍包括法律边界、技术选型、完整实操和避坑经验希望能给想系统学习爬虫的新手一个清晰的方向。这一节没有什么高深莫测的东西就是实打实的基础实战从一个最简单的HTTP请求开始到requests库的基本用法再到XPath解析HTML、数据落盘最后聊一聊服务端常见的反爬策略和我们应该怎么应对。无论你是刚接触Python的小白还是写过一点脚本但没系统梳理过爬虫逻辑的初学者这篇文章都很适合你。1. 内容整体设计与思路拆解1.1 爬虫的本质你只是在替浏览器“读网页”很多人把爬虫想象得很神秘其实它的本质特别简单浏览器能打开一个网页爬虫也能浏览器把网页渲染成好看的样子爬虫则直接拿网页源码来读。换句话说爬虫就是一段“不需要浏览器界面”的HTTP客户端程序替你自动完成“访问网页—抽取数据—保存数据”这个流程。理解了这一点就不难明白爬虫的核心技术点其实有三个发起网络请求、解析返回内容、持久化数据。所有花里胡哨的框架比如Scrapy、PySpider底层本质上都是围绕这三件事打转的。入门阶段不需要碰框架直接用requests加上lxml就能搞定绝大多数简单场景。我在带新人入行时第一件事就是让他们做一个“手工爬虫”用浏览器开发者工具手动看请求、看响应、看DOM结构然后一步步模拟。这个过程会把HTTP协议、HTML结构、字符编码这些最基础的概念全部串起来比直接丢一个Scrapy框架让他们照猫画虎有效得多。1.2 合规不是口号三条红线与一套自查清单既然标题里明确提到了“合规获取网络数据”那这事必须开门见山地说清楚。国内处理爬虫纠纷的案例已经不少了里面有一些共识性的红线我归纳成三条入门阶段就记牢能避掉大部分坑红线一未经授权抓取个人隐私信息。包括但不限于姓名、手机号、身份证号、住址、行踪轨迹、健康信息等。这类数据属于法律明确保护的对象不管抓取手段是否“温和”只要身份可识别就有很高风险。红线二绕过技术保护措施。如果你需要破解验证码、伪造登录凭证、绕过IP封锁、破解签名算法才能拿到数据那基本就已经踩线了。爬虫本身不违法但“突破访问控制”这个行为很容易被认定为非法获取计算机信息系统数据。红线三对目标服务器造成实质性损害或影响正常服务。高并发请求把网站拖垮、大量占用带宽、频繁触发告警这些都不是一句“我只是爬虫”就能开脱的。服务器不是你家的把它弄瘫痪了性质就变了。基于这三条红线我给自己定了一个每次写爬虫前都过一遍的自查清单分享出来供大家参考目标网站有没有robots.txt我是否遵守了里面的Disallow规则目标网站的用户协议里是否明确禁止爬虫采集我要抓的数据是否涉及个人信息目标网站是否有登录门槛、付费墙或反爬验证要“破解”才能拿到吗我的请求频率会不会对目标服务器造成压力抓下来的数据我准备用来做什么是否会二次传播或商业化这些自查项里但凡有一两项是模糊的、心虚的我就不会碰。不是胆子小而是这行干久了见多了翻车的案例知道边界在哪里。1.3 爬虫的学习路径与技术栈选型入门阶段用什么技术栈我给出一个非常“字面意义的基础版”组合Python 3 requests lxml csv/json。这是经过大量实践验证的最低成本方案。Python是爬虫生态最成熟的语言没有之一。requests库提供了比标准库urllib更简单的接口几行代码就能发一个GET请求。lxml库里的etree模块支持XPath语法可以在HTML文档里精准定位元素。最后用Python内置的csv或json模块把数据存下来整个过程零依赖、零配置、零框架。为什么不一开始就上Scrapy因为Scrapy的架构里带了调度器、中间件、Item Pipeline这些东西新手很容易被框架本身的概念绕晕反而忽略了HTTP和HTML解析这些最底层的逻辑。先手写一个完整的小爬虫再用Scrapy重构一遍你会发现自己对爬虫的理解完全不一样。2. 核心细节解析与实操要点2.1 先看懂网页请求头、状态码与页面结构在写第一行爬虫代码之前建议先打开浏览器的开发者工具F12切到Network标签页随便访问一个网页看一次完整的请求-响应过程。你会在里面看到几个关键信息**请求头Request Headers**里最核心的是User-Agent和Referer。User-Agent告诉服务器“我是谁”一个正常的浏览器UA通常长这样Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36很多网站的反爬策略就是看UA——如果UA是一个库的名称比如python-requests/2.31.0一眼就能认出是爬虫直接拒了。所以入门第一课就得养成设置UA的习惯。**状态码Status Code**是服务器给的“回执”。200表示成功301/302是重定向403是拒绝访问404是页面不存在503通常是服务器过载或被限流。看到403和503时先别急着怀疑自己代码写错了大概率是触发了反爬。页面结构则要看Elements标签页找到你想抓的数据在哪个标签下面。这里有一个非常重要的经验你在页面上看到的文字可能不是HTML源码里的原始文字而是JavaScript动态渲染的。如果Elements里能看到但右键“查看网页源代码”里找不到说明这个页面是动态渲染的requests直接抓会拿不到数据。这一节的实操要点是先用浏览器把目标网页的手工请求流程走通确认数据在服务端返回的HTML里再去写爬虫。跳过这一步直接写代码大概率会在一堆动态加载问题里迷失方向。2.2 Requests库写你的第一个GET请求requests库的用法非常直白一分钟就能上手。下面这段代码是爬虫入门必写的第一段import requests url https://example.com/books 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, Accept-Language: zh-CN,zh;q0.9, } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(resp.text[:500])这里面有几个细节值得新手注意timeout参数别省略。如果不设超时时间一旦目标服务器不响应你的程序会一直挂在那里谁等谁崩溃。10秒是我常用的默认值根据网络环境可以调整但一定得有。resp.text可能乱码。requests会从响应头推断编码但如果页面是GBK编码且没有在Header里标明就会出现中文乱码。这时候需要这样处理resp.encoding gbk # 或者 utf-8更保险的做法是直接从响应头里拿编码或者用resp.apparent_encoding来自动检测但这个方法偶尔会猜错实际用下来还是手动指定更靠谱。检查状态码和内容。我见过很多新手一拿到resp.text就直接扔给解析器去处理结果解析不出来才回头查问题。正确做法是先打印状态码和前几百个字符确认拿到的内容确实是HTML且包含目标数据再做下一步。一步一验证是写爬虫最稳的节奏。2.3 从HTML到数据XPath与正则表达式怎么选拿到HTML之后就要从里面把数据抽取出来。这个环节有两个主流工具正则表达式和XPath。正则表达式的强项是处理文本模式比如邮箱、手机号、日期这类有固定格式的内容。但如果拿来解析嵌套的HTML结构就很容易写出又长又脆弱的表达式稍微改版就崩实战中不太推荐首选。XPath则适合处理结构化文档它的写法和文件路径很像比如from lxml import etree html resp.text tree etree.HTML(html) titles tree.xpath(//div[classbook-list]//h2/text()) prices tree.xpath(//div[classbook-list]//span[classprice]/text())这段的意思非常直白找到所有class为book-list的div在它下面找所有h2标签的文本以及class为price的span标签的文本。XPath里最常用的几个写法入门阶段记住这些就够了写法含义示例//在任意层级查找//p找所有p标签/只在子层级查找/div/p找div下的直接p子标签class按属性匹配//div[classxxx]text()取标签文本//h1/text()contains()模糊匹配属性//a[contains(href, detail)]选择XPath而不是CSS选择器主要是因为它调试起来直观在浏览器Console里直接$x(//div)就能测试不用来回重启程序。不过等熟练之后两个工具都可以掌握看场景选择就好。2.4 数据落地JSON、CSV还是数据库爬下来的数据总得有地方放。入门阶段不需要碰MySQL和MongoDBJSON和CSV两个文件格式足够应付绝大多数练习场景。如果数据结构是嵌套的比如一个字典里有列表、列表里再有字典用JSON最合适import json data [ {title: Python入门, price: 59.0}, {title: 爬虫入门, price: 39.0}, ] with open(books.json, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)注意ensure_asciiFalse这个参数如果不加中文会被转义成\uXXXX这样的形式文件没法看。如果是扁平化的表格数据比如一行一条记录、字段固定不变用CSV更合适import csv with open(books.csv, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[title, price]) writer.writeheader() writer.writerows(data)这里我用的是utf-8-sig编码而不是utf-8是因为CSV文件如果直接用Excel打开UTF-8无BOM格式会导致中文乱码加BOM之后问题就没了。这种细节教程里很少会告诉你但实际用的时候一定会遇到。3. 一个完整的入门案例抓取公开图书网站3.1 目标分析与合规确认理论说了这么多我们来走一个完整的案例。为了安全起见我用的是一个专门供爬虫学习者练习的公开测试站点books.toscrape.com这个站点的存在意义就是让人爬没有登录门槛、没有隐私数据、没有付费内容是完全合规的练习目标。在动手之前还是按自查清单过一遍这个网站允许爬虫练习robots.txt里对爬虫无限制抓的数据是公开的图书名称和价格不涉及个人信息请求频率控制得当就不会影响服务。合规确认没问题可以开工。接下来用浏览器打开这个网站F12看一眼页面结构。我观察到图书列表页有这样一个结构模式每本书在一个article classproduct_pod标签里书名在h3 a标签里title属性就是书名价格在p classprice_color里确认了这些信息就可以写代码了。3.2 分步实现请求、解析、抽取、存储完整代码如下这是我能给到的最标准的“基础实战”版本import requests from lxml import etree import csv def fetch_page(url): 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) resp.encoding utf-8 if resp.status_code ! 200: print(f请求失败: {resp.status_code}) return None return resp.text def parse_books(html): tree etree.HTML(html) books [] for item in tree.xpath(//article[classproduct_pod]): title item.xpath(.//h3/a/title)[0] price_text item.xpath(.//p[classprice_color]/text())[0] price float(price_text.replace(£, )) books.append({title: title, price: price}) return books def save_to_csv(books, filename): with open(filename, w, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnames[title, price]) writer.writeheader() writer.writerows(books) html fetch_page(https://books.toscrape.com/catalogue/page-1.html) if html: books parse_books(html) save_to_csv(books, books.csv) print(f共抓取到 {len(books)} 本书已保存到 books.csv)这段代码看起来简单但每一行都有讲究。parse_books函数里的相对路径选择器.//h3/a是很多新手容易写错的地方。注意这里的点号表示从当前item节点内部继续查找如果不加点号//h3会从整篇文档去查找结果就是每一本书都会重复匹配到所有标题。调试这种问题最费时间多一个点少一个点结果完全不一样。价格提取里的replace(£, )是个很典型的细节网页源码里的价格带符号不能直接转float得先清理。实际工作中你会遇到比这更脏的数据比如价格后面跟了“元/斤”、“起”、“包邮”这些杂字符串都需要做清洗甚至用正则抓取数字部分。3.3 请求频率控制与礼貌爬取写完了基础版我再给这段代码加一个“礼貌爬取”的升级如果我们想翻页抓多页数据直接写一个for循环快速抓取很容易给目标服务器造成压力。合规的做法是加一个简单的延时控制import time for page in range(1, 4): url fhttps://books.toscrape.com/catalogue/page-{page}.html html fetch_page(url) if html: books parse_books(html) save_to_csv(books, fbooks_page_{page}.csv) print(f第{page}页抓取完成{len(books)}条数据) time.sleep(2) # 每页间隔2秒这个time.sleep(2)就是礼貌爬取的核心。2秒是我在实际项目里常用的默认值如果目标站点比较慢或者明显不欢迎爬虫可以调到5秒以上。我见过一些极端的案例新手写了个多线程爬虫一秒钟发几十个请求把对方的小网站直接爬挂了结果收到了律师函。入门阶段一定忍住不要碰多线程、异步这种性能优化技巧先学会慢下来。3.4 核心参数选择UA设置、超时时间与重试策略再展开讲几个参数选择背后的逻辑。User-Agent为什么要模拟浏览器因为服务器可以通过UA判断请求来源。用真实的浏览器UA相当于礼貌地告诉服务器“我是正常访客”这属于合理规避技术障碍跟突破访问控制完全是两回事。但要注意UA必须真实有效不要去伪造一个根本不存在的浏览器UA反而容易被识别。超时时间的选择我一般给到10秒。太短容易在慢网环境下误判失败太长则会让程序卡死。如果你的爬虫面向的是国内可以稳定访问的站点5到8秒通常够用如果是跨国站点网络延迟高可以放宽到15秒甚至20秒。重试策略是必须做的但不是简单地重复请求。我的方案是请求失败时第一次重试前等待3秒第二次等待10秒最多重试两次。这个策略背后的逻辑是服务器临时繁忙时给一点缓冲时间往往就能恢复如果连续三次都失败说明问题不在网络波动而在请求本身可能被封了继续重试没有意义。4. 服务器防爬与反爬的基本原理4.1 从“Java Controller层防护”说起服务端怎么做风控爬虫界有句老话你在爬对方在防这场博弈永远存在。我注意到最近网上一批开发者在讨论“Java Controller层如何防护防止爬虫”这个话题其实特别有价值。它从服务端的视角解释了反爬策略是怎么设计的反过来也能帮爬虫学习者理解自己的行为会触发哪些风控机制。简单来说服务端在Controller层也就是处理HTTP请求的入口层做的防护核心无非这几类校验请求头、限制请求频率、检测访问行为模式、设置接口签名。这些策略听起来复杂但换一个角度理解就很简单服务端其实是想分辨“坐在电脑前操作浏览器的人”和“跑在服务器上的脚本”。人的行为有不确定性——鼠标轨迹是不规律的、阅读速度有快有慢、点击按钮的顺序有跳跃而脚本的行为高度规律——请求间隔恒定、访问路径固定、不加载图片和静态资源。所以你会发现反爬不是某一个单一技术而是一套综合的行为画像。入门爬虫时知道这一点很有用不要把自己的请求写得“像一个机器人”这本身就是降低被识别风险的最有效手段。4.2 常见的反爬策略与合规应对思路我把常见的反爬策略归纳成一张表每一种都附上合规应对思路这是入门阶段必须掌握的认知框架反爬手段典型表现合规应对思路User-Agent校验非浏览器UA直接拒绝设置真实的浏览器UA访问频率限制短期内大量请求触发封IP降低请求频率加入随机延时Cookie/Session校验需要登录才能访问只爬公开数据不碰登录接口接口签名请求参数中带加密签名优先寻找官方公开API行为分析请求轨迹过于规律被识别模拟正常浏览节奏避免机械循环动态渲染数据通过JS异步加载寻找更底层的公开数据接口或放弃这里有一个特别重要的认知对动态渲染页面合规的做法是去Network面板里找XHR请求看看数据是不是通过某个公开的JSON接口返回的。很多时候这个接口就是普通的数据请求不需要任何破解就能直接访问只是很多新手不知道。找到它爬虫反而更简单。如果目标是App那就更清楚了抓包看一下App调用的API很多也是公开的。这里的前提是接口本身不要求破解签名、不要求伪造身份否则就属于越界了不建议碰。4.3 为什么“伪装”不等于“攻击”我在很多爬虫交流群里看到有人问“怎么隐藏自己”、“怎么绕过防护”这些提问方式本身就反映出对合规边界的模糊认识。我想把话说得更明白一点设置一个正常的User-Agent、控制访问频率、遵循robots协议这些叫“合理使用”目的是降低对目标服务器的干扰和你在浏览器里正常访问网站没有本质区别。而伪造登录态、破解签名算法、暴力枚举接口参数、逆向App加固逻辑这些叫“突破访问控制”已经超出了学习爬虫的范畴是在挑战安全边界。我在实际带人时有一条硬性标准如果一个数据需要你“破坏”某种机制才能拿到那它大概率不应该被你拿到。这条标准在入门阶段可能会挡住很多“有意思的练手项目”但长远来看它能保护你不踩坑。学爬虫是为了高效获取公开数据不是为了证明自己技术有多厉害。这两者之间的分寸感值得每一个学习者认真品味。5. 常见问题与排查技巧实录5.1 问题速查表把我在实际开发里遇到的问题浓缩成一张速查表每一个都是真实踩过的坑问题现象可能原因解决方案返回403UA被识别或被封IP设置浏览器UA检查请求频率返回200但拿不到数据页面是JS动态渲染到Network面板找XHR数据接口中文乱码编码判断错误手动指定resp.encodingXPath提取到空列表表达式写错或结构判断错误在浏览器Console里用$x()调试XPath重复提取相同数据相对路径漏写.//将表达式改为.//...抓到一半IP被封请求频率过高加入time.sleep降低并发CSV用Excel打开乱码编码不是utf-8-sig写入时用utf-8-sig页面内容有变化导致解析失败网站改版重新审查页面结构更新选择器这些问题的共性是绝大多数爬虫调试问题都不是逻辑难题而是细节问题。你花了大半天排查一个XPath提取不到数据的问题最后发现只是少了一个点号这种事在爬虫坑里太常见了。所以调试的时候要有耐心一步步打印中间结果不要一上来就怀疑人生。5.2 五个值得养成的调试习惯长期写爬虫的人都会形成自己的调试套路我分享几个每天都用得上的习惯对新手尤其有价值。第一个习惯是一步一打印。请求完页面先打印状态码和内容长度解析完先打印前三条提取结果存储完打开文件看一眼。每一步都确认通过再进行下一步能省掉大量回头排查的时间。第二个习惯是在浏览器里先验证选择器。用Chrome开发者工具的Console直接输入$x(//div[classbook]//h2/text())确认能选中目标元素再写进代码里。浏览器就是最好的XPath调试器。第三个习惯是把响应内容保存成HTML文件。当页面结构复杂时直接把resp.text写到一个html文件里用浏览器打开右键检查比反复在IDE和浏览器之间切换要方便得多。第四个习惯是设置合理的日志输出。不要在关键节点只打一个print(ok)要把上下文信息打出来比如“第3页抓取完成成功解析20条数据耗时1.2秒”。这种日志在排查问题时非常有用。第五个习惯是尊重错误信息。Python报错信息里大多数时候已经告诉了你问题在哪认真读完、逐行分析比直接复制报错去搜索引擎问要高效得多。尤其是IndexError、KeyError这类错误十有八九是数据结构和你预期的不一样打印出来看看就知道了。养成这些习惯之后你会发现写爬虫的调试成本大幅降低。外人看爬虫好像很酷干久了才知道真正的高手不是爬得快而是出了问题能快速定位。5.3 多页抓取与去重从单页走向完整项目当你能成功抓取单页数据之后下一步很自然地就是要抓取多页数据。这里除了前面提到的频率控制还有一个容易忽视的问题需要处理爬虫通常会重复请求同一个URL导致数据重复存储。解决方案是在写入文件之前对URL或者数据主键做一个去重判断。最简单的办法是维护一个set集合在解析到每条数据时检查它的URL是否已经在集合里如果不在再写入seen set() def is_duplicate(url): if url in seen: return True seen.add(url) return False这个方案在小规模爬虫里完全够用但如果数据量扩大到几十万条就要考虑用数据库的唯一索引来做去重了。入门阶段先把set方案搞清楚理解“去重”这个需求的本质后面再看那些重型方案就轻松了。另外一点多页爬虫写完后我建议把整个流程封装成函数把URL、解析函数、延时时间作为参数传进去。这样将来不管是换个网站爬还是把脚本扩展成Scrapy项目底层逻辑都通用。框架会变但“请求—解析—去重—存储”这个流程不会变。写在最后的一点个人体会爬虫入门说难不难说简单也不简单。不难在于它本质上就是三次函数调用发请求、解析、存储半天就能学会不简单在于真正让一个爬虫项目稳定运行、合规落地靠的是对HTTP协议的熟悉程度、对数据结构的敏感度、对网站规则的敬畏心。我见过太多人写代码两小时调试两整天最后才发现是少了一个点号或者忘了设UA也见过太多人技术没问题但因为踩了合规的线吃了个大亏。学爬虫最值钱的东西从来不是技巧本身而是判断力——什么该爬、什么不该爬、以什么方式爬这个尺度理清楚了你的技术才能真正为你的目标服务。这一节的基础实战希望你已经上手写了几行代码也记住了一条底线后续再往深度走路会顺很多。