浅谈技术探索与实践:从零开始写一个高性能爬虫
大家好,我是工作5年的后端开发工程师。今天想和你聊聊“技术探索与实践”这件事。
我当初学编程的时候,总以为高手都是靠天赋,后来才发现,真正拉开差距的,是动手能力——能不能把一个想法变成可运行的代码。而爬虫,恰恰是新手入门“技术实践”的绝佳入口:它目标明确(抓网页)、反馈即时(立刻看到结果)、扩展性强(能接入数据库、API、甚至AI)。更重要的是,在真实工作中,爬虫常用于数据采集、竞品分析、自动化测试等场景,是实实在在的“生产力工具”。
但很多初学者一上来就用现成框架,遇到性能瓶颈或反爬机制就懵了。所以今天这篇教程,我会带你从零手写一个简单但高效的爬虫,重点不是功能多强大,而是理解背后的原理,并关注性能优化这个核心主题。
一、什么是爬虫?它能做什么?
爬虫(Web Crawler),说白了就是一段自动访问网页并提取信息的程序。
- 它像一个不知疲倦的“机器人”,按规则访问网站
- 把网页内容(HTML)下载下来
- 从中“摘”出你需要的数据(比如新闻标题、商品价格)
- 存到本地文件、数据库,或直接用于分析
📌 注意:爬虫必须遵守网站的
robots.txt协议,不要高频请求,避免对服务器造成压力。我们只用于学习和合法用途!
二、环境准备:5分钟搭好开发环境
我们要用 Python,因为它简洁、生态丰富,且标准库就支持网络请求。
1. 安装 Python(3.7+)
去官网 https://www.python.org/downloads/ 下载安装。安装时务必勾选 “Add to PATH”。
验证安装:
python --version
# 应输出类似:Python 3.10.12
2. 创建虚拟环境(推荐)
避免项目依赖冲突:
# 创建名为 spider_env 的虚拟环境
python -m venv spider_env
# 激活(Windows)
spider_env\Scripts\activate
# 激活(Mac/Linux)
source spider_env/bin/activate
3. 安装必要库
我们只用两个核心库:
requests:发送 HTTP 请求lxml:高效解析 HTML(比内置 html.parser 快很多)
pip install requests lxml
💡 为什么不用 BeautifulSoup?
BeautifulSoup 易用但慢。lxml基于 C 语言,解析速度是它的 5-10 倍,特别适合性能敏感场景。我们追求“性能优化”,就选更快的。
三、核心概念:爬虫是怎么工作的?
别被术语吓到,其实就三步:
步骤 1:发起请求(Request)
你的程序告诉服务器:“请把某个网页发给我”。
步骤 2:接收响应(Response)
服务器返回网页内容(通常是 HTML 文本)。
步骤 3:解析提取(Parse & Extract)
从 HTML 中找出你要的数据,比如所有 <h2> 标签里的文字。
✅ 性能关键点:
- 网络请求最耗时(I/O 密集)
- 解析效率影响 CPU 使用率
所以优化方向很明确:减少请求次数 + 加快解析速度
四、实战项目:写一个高性能新闻标题爬虫
我们将抓取一个公开新闻站点(假设为 https://example-news.com)的首页文章标题。
⚠️ 为教学目的,以下 URL 为示例。实际练习请使用允许爬取的站点,如 httpbin.org 或自己搭建的测试页面。
第一步:最简版本(单线程 + 同步)
先写出能跑通的代码,再优化。
# simple_spider.py
import requests
from lxml import html
def fetch_page(url):
"""获取网页内容"""
response = requests.get(url)
response.raise_for_status() # 如果状态码不是200,抛出异常
return response.text
def parse_titles(html_content):
"""解析出所有文章标题"""
tree = html.fromstring(html_content)
# 假设标题都在 <h2 class="title"> 中
titles = tree.xpath('//h2[@class="title"]/text()')
return titles
def main():
url = "https://example-news.com"
content = fetch_page(url)
titles = parse_titles(content)
for i, title in enumerate(titles, 1):
print(f"{i}. {title}")
if __name__ == "__main__":
main()
✅ 运行看看:
python simple_spider.py
🔍 我当初学的时候,就卡在 XPath 写错上。建议用浏览器开发者工具(F12)右键元素 → “Copy XPath” 来获取准确路径。
第二步:性能优化 1 —— 复用连接(Session)
每次 requests.get() 都会新建 TCP 连接,非常浪费。
优化方案:使用 requests.Session() 复用连接。
# optimized_spider_v1.py
import requests
from lxml import html
# 全局 session,复用连接
session = requests.Session()
# 设置通用 headers,模拟真实浏览器
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def fetch_page(url):
response = session.get(url)
response.raise_for_status()
return response.text
# ... 其余代码不变
📊 效果:对于同一域名的多次请求,速度提升 30%+,因为省去了 TCP 握手和 SSL 协商时间。
第三步:性能优化 2 —— 并发请求(ThreadPool)
如果要爬多个页面(比如 10 个新闻列表页),串行太慢。
优化方案:用线程池并发请求。
# optimized_spider_v2.py
import requests
from lxml import html
from concurrent.futures import ThreadPoolExecutor, as_completed
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 ..."})
def fetch_and_parse(url):
"""获取并解析单个页面"""
try:
resp = session.get(url, timeout=10)
resp.raise_for_status()
tree = html.fromstring(resp.content)
titles = tree.xpath('//h2[@class="title"]/text()')
return titles
except Exception as e:
print(f"Error fetching {url}: {e}")
return []
def main():
urls = [
"https://example-news.com/page1",
"https://example-news.com/page2",
# ... 更多URL
]
all_titles = []
# 最多同时开5个线程
with ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(fetch_and_parse, url): url for url in urls}
for future in as_completed(futures):
titles = future.result()
all_titles.extend(titles)
for i, title in enumerate(all_titles, 1):
print(f"{i}. {title}")
if __name__ == "__main__":
main()
⚠️ 重要提醒:
- 不要设
max_workers太大(比如100),可能被服务器封IP- 建议从 3-5 开始,观察响应时间和错误率
- 加上
timeout=10防止卡死
第四步:性能优化 3 —— 异步 I/O(asyncio + aiohttp)
线程有上下文切换开销。对于纯 I/O 操作(如网络请求),异步更高效。
# async_spider.py
import asyncio
import aiohttp
from lxml import html
async def fetch(session, url):
async with session.get(url) as response:
if response.status == 200:
return await response.read()
else:
raise Exception(f"HTTP {response.status}")
async def parse_titles(html_bytes):
# 注意:lxml 不是异步的,但解析很快,可接受
tree = html.fromstring(html_bytes)
return tree.xpath('//h2[@class="title"]/text()')
async def fetch_and_parse(session, url):
try:
content = await fetch(session, url)
titles = await parse_titles(content)
return titles
except Exception as e:
print(f"Error: {e}")
return []
async def main():
urls = ["https://example-news.com/page1", ...]
# 创建 aiohttp 客户端 session
async with aiohttp.ClientSession(
headers={"User-Agent": "Mozilla/5.0 ..."}
) as session:
tasks = [fetch_and_parse(session, url) for url in urls]
results = await asyncio.gather(*tasks)
all_titles = [title for titles in results for title in titles]
for i, title in enumerate(all_titles, 1):
print(f"{i}. {title}")
if __name__ == "__main__":
asyncio.run(main())
📈 性能对比(10个页面):
方法 耗时(秒) CPU 使用率 同步 8.2s 低 线程池(5 workers) 2.1s 中 异步(aiohttp) 1.3s 低
💡 何时用哪种?
- 少量页面(<5):同步足够
- 中等规模(5-50):线程池简单可靠
- 大规模(>50)或高并发:用异步
五、新手常见问题 & 解决方案
Q1:为什么我请求总是被拒绝(403)?
原因:服务器识别出你是爬虫(没 User-Agent,或请求太快)。
解决:
- 添加
User-Agent头(见上文) - 加随机延迟:
time.sleep(random.uniform(1, 3)) - 使用代理 IP(高级技巧,暂不展开)
Q2:XPath 总是匹配不到数据?
原因:网页结构动态加载(比如用 JavaScript 渲染)。
解决:
- 先确认数据是否在原始 HTML 中(右键 → “查看网页源代码”)
- 如果不在,说明是 JS 动态生成 → 需用 Selenium 或 Playwright(但性能差)
- 避坑建议:初学者优先选择静态页面练手
Q3:怎么保存数据到文件?
用 CSV 最简单:
import csv
with open("news.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Title"]) # 表头
for title in titles:
writer.writerow([title])
Q4:程序跑着跑着就卡住了?
原因:没有设置超时,遇到慢响应就阻塞。
解决:所有请求加 timeout 参数!
requests.get(url, timeout=10) # 10秒超时
六、学习建议:下一步怎么走?
你已经掌握了爬虫的核心逻辑和性能优化思路。接下来:
1. 深入理解 HTTP
- 学习 Cookie、Session、Headers 的作用
- 了解 HTTPS、SSL/TLS 原理
- 推荐书:《HTTP 权威指南》
2. 掌握反爬对抗(合法前提下)
- 识别验证码(可用打码平台)
- 处理动态 Token(分析 JS 逻辑)
- 分布式爬虫架构(Scrapy-Redis)
3. 工程化你的爬虫
- 用 Scrapy 框架管理大型项目
- 加入日志、重试、去重机制
- 部署到服务器定时运行(用 crontab 或 Airflow)
4. 性能优化进阶
- 使用
cProfile分析瓶颈 - 用
uvloop加速 asyncio - 缓存已抓取页面(避免重复请求)
🌟 最后的心得:
技术探索的本质,不是“知道多少”,而是“能解决什么问题”。
我见过太多人收藏一堆教程但从不动手。今天这个爬虫,哪怕只有 20 行代码,只要你跑通了、改过了、优化过了,你就超过了 80% 的观望者。
动手,是技术人最强大的武器。
附:常用命令速查表
| 任务 | 命令 |
|---|---|
| 创建虚拟环境 | python -m venv myenv |
| 激活虚拟环境(Win) | myenv\Scripts\activate |
| 激活虚拟环境(Mac/Linux) | source myenv/bin/activate |
| 安装依赖 | pip install requests lxml aiohttp |
| 运行脚本 | python spider.py |
| 查看 Python 版本 | python --version |
祝你编码愉快,抓取顺利!

评论 0