网络爬虫基础:从HTTP到数据提取

做舆情分析,第一步就是拿到数据。怎么拿?说白了,就是模拟人去访问网页的过程。我刚开始接触爬虫时,以为它多神秘,后来发现核心就三件事:发请求、收响应、提取信息。今天咱们就把这三件事拆开揉碎了讲清楚。

本章知识体系:HTTP协议 → Requests库 → BeautifulSoup解析 → XPath/CSS选择器

网络爬虫基础 HTTP协议与请求原理 Requests库入门 BeautifulSoup解析HTML XPath与CSS选择器 GET/POST · 状态码 · 请求头 get() · post() · Session find() · select() · 遍历 //div · .class · #id

HTTP协议:爬虫的交通规则

HTTP协议,说白了就是浏览器和服务器之间沟通的「暗号」。你想想看,你在地址栏敲个网址,回车,背后发生了什么?

我习惯把HTTP请求想象成你去餐厅点菜:

  • 请求行:相当于你说「服务员,我要一份宫保鸡丁」—— 这就是GET请求
  • 请求头:相当于你补充「少放辣,多放花生」—— 这是附加信息
  • 请求体:如果你要提交表单,就像你填了个菜单递给服务员—— 这是POST请求

服务器收到后,会返回一个响应。响应里有个东西特别重要——状态码。我在项目中遇到过无数次,爬虫跑着跑着突然报错,一看状态码:

状态码 含义 常见场景
200 请求成功 正常返回数据
301/302 重定向 网址变了,自动跳转
403 禁止访问 被反爬了,需要加请求头
404 页面不存在 链接失效或写错了
500 服务器内部错误 对方服务器崩了,过会儿再试

避坑提醒:我曾经遇到过一个舆情网站,明明页面能打开,但爬虫就是拿不到数据。查了半天,发现是缺少 User-Agent 请求头。服务器一看「咦,没有浏览器标识,肯定是爬虫」,直接返回403。加个常见的浏览器UA就解决了。

Requests库:Python里的HTTP利器

Python里发HTTP请求,我个人最常用的是 requests 库。为啥?因为它太直观了。你看:

import requests

# GET请求 - 获取网页
url = "https://example.com/news"
response = requests.get(url)
print(response.text)  # 打印HTML内容

# POST请求 - 提交数据
data = {"keyword": "舆情", "page": 1}
response = requests.post(url, data=data)
print(response.json())  # 如果返回JSON

嗯,这里要注意几个细节:

  • response.text 返回的是字符串格式的HTML
  • response.content 返回的是字节流,适合下载图片
  • response.status_code 可以检查状态码

我建议你养成一个习惯:每次发请求前,先伪造一下请求头。很多网站对爬虫不友好,但加个 headers 参数就能伪装成正常浏览器:

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://www.baidu.com/"
}
response = requests.get(url, headers=headers)

小技巧:如果你需要保持登录状态(比如爬取需要登录的舆情平台),用 requests.Session()。它会自动帮你管理Cookie,就像浏览器一样记住你是谁。

BeautifulSoup:HTML的「解剖刀」

拿到HTML源码后,怎么提取我们需要的信息?BeautifulSoup 就是干这个的。它能把杂乱的HTML变成一棵结构清晰的树,然后我们在这棵树上「摘果子」。

安装很简单:pip install beautifulsoup4

基本用法:

from bs4 import BeautifulSoup

html = "<html><body><h1>舆情标题</h1><p class='content'>正文内容</p></body></html>"
soup = BeautifulSoup(html, 'html.parser')

# 提取标题
title = soup.find('h1').text
print(title)  # 输出:舆情标题

# 提取正文
content = soup.find('p', class_='content').text
print(content)  # 输出:正文内容

我在项目中遇到过最头疼的情况是——网页结构不规范。有的标签没闭合,有的属性值带空格。BeautifulSoup 的 html.parser 解析器比较宽容,但遇到复杂的页面,我建议换成 lxml 解析器,速度更快,容错性也更好。

常用的查找方法:

  • find():找第一个匹配的元素
  • find_all():找所有匹配的元素,返回列表
  • select():用CSS选择器查找,后面会讲

实战经验:舆情数据通常藏在 <div><span> 标签里。我一般先打印出 soup.prettify(),看看HTML结构长什么样,再决定用什么选择器。别一上来就写代码,先观察,再动手。

XPath与CSS选择器:精准定位的艺术

BeautifulSoup 的 find() 方法虽然好用,但遇到深层嵌套的HTML,写起来就有点啰嗦了。这时候,XPath 和 CSS 选择器就派上用场了。

XPath:用路径说话

XPath 就像文件路径一样,从根节点一步步定位到目标元素。我刚开始学的时候觉得它语法怪,用多了才发现真香。

from lxml import html

tree = html.fromstring(response.content)

# 绝对路径:从根开始
title = tree.xpath('/html/body/div[1]/h1/text()')

# 相对路径:用 // 跳过中间层级
titles = tree.xpath('//h1/text()')

# 按属性筛选
items = tree.xpath('//div[@class="news-item"]/text()')

# 按文本内容筛选
result = tree.xpath('//p[contains(text(), "舆情")]/text()')

常用的XPath表达式:

表达式 含义
/ 从根节点选取
// 从任意位置选取
. 当前节点
.. 父节点
@ 选取属性,如 @class
text() 选取文本内容

曾经踩过的坑:XPath 的索引是从1开始的,不是0!我第一次用 //div[1] 想取第一个div,结果取到了第二个。查了半天文档才发现这个「坑」。记住了,XPath里 [1] 就是第一个,不是第零个。

CSS选择器:前端工程师的最爱

如果你写过CSS,那CSS选择器对你来说就是老朋友了。BeautifulSoup 的 select() 方法直接支持CSS选择器:

# 标签选择器
soup.select('h1')

# 类选择器
soup.select('.news-title')

# ID选择器
soup.select('#main-content')

# 属性选择器
soup.select('a[href]')  # 所有带href属性的a标签
soup.select('a[href*="weibo"]')  # href包含"weibo"的a标签

# 组合选择器
soup.select('div.news-item > h2')  # 直接子元素
soup.select('div p')  # 后代元素

我个人习惯:简单的页面用CSS选择器,因为写起来快;复杂的页面用XPath,因为表达能力更强。你想想看,CSS选择器能做的,XPath基本都能做,但反过来不一定。

调试小技巧:在浏览器里按F12打开开发者工具,选中目标元素,右键 → Copy → Copy XPath 或 Copy selector。直接粘贴到代码里,省时省力。但注意,浏览器生成的XPath有时过于冗长,我会手动精简一下。

实战组合:一个完整的舆情数据抓取流程

把今天学的串起来,写一个完整的例子:

import requests
from bs4 import BeautifulSoup

# 1. 发请求
headers = {"User-Agent": "Mozilla/5.0"}
url = "https://example.com/yuqing"
resp = requests.get(url, headers=headers)

# 2. 检查状态
if resp.status_code == 200:
    # 3. 解析HTML
    soup = BeautifulSoup(resp.text, 'lxml')
    
    # 4. 提取数据
    articles = soup.select('div.article-item')
    for art in articles:
        title = art.find('h2').text.strip()
        summary = art.find('p', class_='summary').text.strip()
        print(f"标题:{title}\n摘要:{summary}\n---")
else:
    print(f"请求失败,状态码:{resp.status_code}")

这个流程,说白了就是爬虫的「三板斧」。我在做舆情监控系统时,每天就是用这个模式,定时抓取几十个网站的数据。稳定运行了大半年,没出过问题。

好了,HTTP协议、Requests库、BeautifulSoup、XPath和CSS选择器,这些就是爬虫的「基本功」。练熟了,后面学Scrapy框架、动态页面抓取,都会轻松很多。


公众号:蓝海资料掘金营,微信deep3321