八:爬虫入门——用 requests 抓取网页数据

一、回顾与本篇目标

上一篇我们学了文件读写,你的 Python 程序可以把数据持久化到硬盘上了。这一篇,我们要学一个让 Python 声名大噪的技能——网络爬虫

爬虫,通俗地说就是让程序自动访问网页,把上面的数据抓取下来。比如自动抓取新闻标题、批量下载图片、监控商品价格变化、收集招聘信息。Python 是爬虫领域的王者语言,因为它的语法简洁、网络请求库强大、HTML 解析工具丰富。

如果你之前跟着《后端零基础入门》系列学过 Node.js,你可能记得后端服务是接收请求然后返回数据。爬虫正好反过来了——它是发出请求,然后解析别人返回的数据。

本篇的目标:

  1. 理解 HTTP 请求和响应的基本概念(爬虫的底层逻辑)
  2. 安装并使用 requests 库发送 GET 和 POST 请求
  3. 学会用 BeautifulSoup 解析 HTML 并提取数据
  4. 处理翻页,抓取多页数据
  5. 设置请求头,伪装成浏览器
  6. 写出第一个完整的爬虫脚本

二、爬虫的底层逻辑:HTTP 请求和响应

在你写爬虫代码之前,先理解爬虫到底在干什么。这和你在浏览器里访问网页是同一件事。

当你在浏览器地址栏输入 https://www.example.com 并按回车,浏览器做了这些事:

  1. 向服务器发送一个 HTTP 请求(Request)。
  2. 服务器收到请求后,返回一个 HTTP 响应(Response)。
  3. 浏览器解析响应中的 HTML 代码,渲染成你看到的网页。

爬虫做的事情完全一样——只不过把浏览器换成了 Python 程序。Python 程序发送请求,拿到服务器返回的 HTML,然后从中提取出你需要的数据。

一个 HTTP 请求包含几个关键信息:

  • URL:你要访问哪个网页(如 https://www.example.com/news)。
  • 请求方法:最常见的是 GET(获取数据)和 POST(提交数据)。
  • 请求头(Headers):附加信息——用的什么浏览器、接受什么格式的数据等。
  • 请求体(Body):只有 POST 请求才有,是提交给服务器的数据。

一个 HTTP 响应包含:

  • 状态码:200 表示成功,404 表示页面不存在,500 表示服务器出错。
  • 响应头:服务器返回的附加信息。
  • 响应体:服务器返回的实际内容——HTML 代码、JSON 数据等。

爬虫本质上就是:组装请求 → 发送 → 接收响应 → 解析响应体 → 提取数据

三、安装 requests 库

Python 自带的 urllib 也能发 HTTP 请求,但用起来比较繁琐——需要手动构建请求对象、设置编码、处理异常。requests 是第三方库,把这些繁琐的细节全部封装好了,代码量减少一半以上。它是 Python 爬虫的事实标准。

在虚拟环境中安装:

pip install requests

验证安装:

python -c "import requests; print(requests.__version__)"

如果输出版本号,说明安装成功。

四、你的第一个爬虫:获取网页内容

import requests

# 发送 GET 请求
response = requests.get('https://www.example.com')

# 状态码
print(f'状态码:{response.status_code}')

# 响应头
print(f'内容类型:{response.headers["Content-Type"]}')

# 响应体(HTML 代码)
print(f'网页内容(前 200 个字符):{response.text[:200]}')

逐行解释:

  • requests.get(url):发送一个 GET 请求到指定的 URL。这是爬虫中最常用的操作。
  • response.status_code:HTTP 状态码。200 表示请求成功,404 表示页面不存在。
  • response.headers:服务器返回的响应头,是一个字典。通过 'Content-Type' 键可以知道服务器返回的是什么类型的内容。
  • response.text:响应体的文本内容(通常是 HTML 代码)。它是 Python 字符串,你可以对它做任何字符串操作——切片、查找、替换。
  • response.content:响应体的二进制内容(用于下载图片、视频等非文本数据)。

几个常用的 response 属性:

属性 含义 示例
response.status_code HTTP 状态码 200、404、500
response.text 响应体的文本内容 HTML 字符串
response.content 响应体的二进制内容 图片、视频的字节数据
response.json() 把 JSON 响应体解析成 Python 字典 API 返回的 JSON 数据
response.headers 响应头字典 response.headers['Content-Type']
response.encoding 响应的编码 'utf-8'
response.url 最终请求的 URL 可能和请求时的 URL 不同(发生了重定向)

处理请求异常

网络请求可能失败——服务器挂了、网络断了、URL 写错了。一个健壮的爬虫需要处理这些异常:

import requests

url = 'https://www.example.com'

try:
    response = requests.get(url, timeout=10)  # 设置超时时间
    response.raise_for_status()  # 如果状态码不是 200,抛出异常
    print('请求成功')
    print(response.text[:200])
except requests.exceptions.Timeout:
    print('请求超时,请检查网络连接')
except requests.exceptions.ConnectionError:
    print('连接失败,请检查 URL 是否正确')
except requests.exceptions.HTTPError as e:
    print(f'HTTP 错误:{e}')
except requests.exceptions.RequestException as e:
    print(f'请求出错:{e}')

关键点:

  • timeout=10:设置超时时间(秒)。如果服务器 10 秒内没响应,就放弃等待。永远不要省略超时设置,否则程序可能永远卡住。
  • response.raise_for_status():如果 HTTP 状态码是 4xx 或 5xx,自动抛出异常。比手动检查状态码更简洁。

五、解析 HTML:BeautifulSoup

requests 只能拿到网页的原始 HTML 代码——一大坨字符串。要从里面提取出具体的数据(比如所有新闻标题、所有图片地址),需要解析 HTML

BeautifulSoup 是 Python 中最流行的 HTML 解析库。它能把 HTML 字符串变成一棵可遍历的树,让你像用 CSS 选择器一样提取元素。

安装 BeautifulSoup

pip install beautifulsoup4

BeautifulSoup 还需要一个解析器来实际分析 HTML。推荐 lxml,速度快:

pip install lxml

基本用法

from bs4 import BeautifulSoup

# 假设这是我们从网页上拿到的 HTML
html = '''

欢迎来到我的网站

第一篇文章

这是第一篇文章的摘要

第二篇文章

这是第二篇文章的摘要

  • Python
  • 爬虫
  • 数据分析

  

'''

# 创建 BeautifulSoup 对象
soup = BeautifulSoup(html, 'lxml')

# 1. 获取标题标签
print(soup.title)            #
print(soup.title.text)       # '测试页面' —— 只取文字内容

# 2. 获取 h1 标签
print(soup.h1.text)          # '欢迎来到我的网站'

# 3. 获取所有链接
for link in soup.find_all('a'):
    print(link['href'], link.text)  # /post/1 第一篇文章   /post/2 第二篇文章

# 4. 按类名查找
articles = soup.find_all('div', class_='article')
print(len(articles))         # 2

逐行解释:

  • BeautifulSoup(html, 'lxml'):把 HTML 字符串解析成一棵文档树。'lxml' 是解析器的名字。
  • soup.title:通过标签名直接访问,拿到第一个匹配的标签。类似 JavaScript 的 document.querySelector('title')
  • 标签.text:获取标签内部的纯文本内容,去掉所有 HTML 标签。
  • soup.find_all('a'):找到所有 <a> 标签,返回一个列表。类似 JavaScript 的 document.querySelectorAll('a')
  • 标签['href']:访问标签的属性,和字典的键访问语法一样。
  • class_='article':注意 class 后面跟了一个下划线。因为 class 是 Python 的关键字,BeautifulSoup 用 class_ 来避免冲突。

BeautifulSoup 的四种查找方式

方法 作用 返回
soup.find('标签名') 查找第一个匹配的标签 单个标签对象 或 None
soup.find_all('标签名') 查找所有匹配的标签 列表
soup.select('CSS选择器') 用 CSS 选择器查找 列表
soup.select_one('CSS选择器') 用 CSS 选择器查找第一个 单个标签对象 或 None

如果你熟悉前端开发,推荐用 select()select_one()——直接写 CSS 选择器,和 document.querySelectorAll() 完全一致:

# CSS 选择器方式(前端开发者更熟悉)
soup.select('.article')              # 类选择器
soup.select('#main')                 # ID 选择器
soup.select('div.article > h2 > a')  # 后代选择器
soup.select('a[href^="/post"]')      # 属性选择器

六、实战:爬取一个真实网页

下面我们用所学知识,写一个完整的爬虫——抓取一个示例网站的文章标题和链接。

import requests
from bs4 import BeautifulSoup

# 目标:抓取一个提供示例文章的网站
url = 'https://books.toscrape.com/'

# 发送请求
response = requests.get(url, timeout=10)
response.raise_for_status()

# 解析 HTML
soup = BeautifulSoup(response.text, 'lxml')

# 找到所有书籍
books = soup.select('.product_pod')

print('===== 书籍列表 =====')
for book in books[:10]:  # 只取前 10 本
    # 书名在 img 标签的 alt 属性中
    title = book.select_one('img')['alt']
    # 价格在 .price_color 标签中
    price = book.select_one('.price_color').text
    print(f'《{title}》 - {price}')

print(f'\n共找到 {len(books)} 本书')

代码解析:

  • soup.select('.product_pod'):用 CSS 类选择器找到所有书籍容器。每个 .product_pod 包含一本书的信息。
  • book.select_one('img')['alt']:在每本书的容器内,找到 <img> 标签,提取 alt 属性的值作为书名。
  • book.select_one('.price_color').text:找到价格标签,提取其中的文本内容。

七、处理翻页:抓取多页数据

大多数网站的数据不是一页展示完的,需要翻页。爬虫需要自动构造每一页的 URL 并依次抓取。

翻页通常有两种模式:

  • URL 中有页码参数:如 ?page=1?page=2。直接用 for 循环改页码就行。
  • 下一页按钮:从页面中找到“下一页”的链接,提取它的 URL,继续抓取。

我们演示第二种——更通用:

import requests
from bs4 import BeautifulSoup

base_url = 'https://books.toscrape.com/catalogue/'
current_url = base_url + 'page-1.html'
page_count = 0
max_pages = 5  # 限制最多抓 5 页,防止爬太猛

while current_url and page_count < max_pages:
    page_count += 1
    print(f'\n===== 第 {page_count} 页 =====')

    # 发送请求
    response = requests.get(current_url, timeout=10)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, 'lxml')

    # 抓取当前页的书籍
    books = soup.select('.product_pod')
    for book in books[:5]:
        title = book.select_one('img')['alt']
        price = book.select_one('.price_color').text
        print(f'  《{title}》 - {price}')

    # 找到“下一页”按钮
    next_button = soup.select_one('.next > a')
    if next_button:
        # 提取下一页的相对路径,拼成完整 URL
        next_href = next_button['href']
        current_url = base_url + next_href
    else:
        current_url = None  # 没有下一页了,停止循环

print(f'\n抓取完成,共抓取 {page_count} 页')

关键逻辑:

  • current_url 保存当前要抓取的页面 URL,每抓完一页就更新为下一页的 URL。
  • soup.select_one('.next > a'):查找“下一页”的链接。.next > a 是 CSS 选择器,表示“类名为 next 的元素下的直接子元素 a”。
  • 如果找不到“下一页”链接,next_buttonNone,循环自然结束。
  • max_pages 设置了抓取上限——实际写爬虫时要控制抓取速度,不要给目标服务器造成太大压力。

八、设置请求头:伪装成浏览器

有些网站会检测访问者是不是爬虫,如果是就拒绝访问。最简单的反爬措施是检查 User-Agent 请求头——它告诉服务器“我是什么浏览器”。

requests 默认的 User-Agent 是 python-requests/2.x.x,等于直接告诉服务器“我是爬虫”。我们可以把它改成浏览器的 User-Agent:

# 不伪装(服务器一眼就看出你是爬虫)
# response = requests.get(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',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
}

response = requests.get(url, headers=headers, timeout=10)

这个 User-Agent 是从哪里来的? 打开你浏览器的开发者工具(F12),切换到 Network 标签,随便点一个请求,在 Request Headers 里就能找到你自己的 User-Agent,直接复制过来用。

除了 User-Agent,有时候还需要加其他头信息(如 RefererCookie),具体要看目标网站的要求。

九、发送 POST 请求

有些数据不是直接展示在页面上的,需要先提交表单才能获取——比如登录后才能看到的数据、搜索框里输入关键词后返回的结果。

POST 请求就是用来提交数据的。以下是一个模拟登录的示例(使用一个专门用于测试的网站):

import requests

# 目标:一个专门用于测试 POST 请求的网站
url = 'https://httpbin.org/post'

# POST 请求的数据
data = {
    'username': '张三',
    'password': '123456',
    'remember': 'on'
}

# 发送 POST 请求
response = requests.post(url, data=data, timeout=10)

# 查看响应(这个网站会把收到的数据原样返回)
result = response.json()
print(f'发送的数据:{result["form"]}')

如果目标 API 接受 JSON 格式的数据,用 json 参数代替 data 参数:

# 发送 JSON 数据(目标 API 需要 JSON 格式时使用)
response = requests.post(url, json={'key': 'value'}, timeout=10)

json 参数会自动把字典转成 JSON 字符串,并设置 Content-Type: application/json 请求头。

十、下载图片等二进制文件

爬虫不只是抓文本,也经常需要下载图片、PDF、视频等二进制文件。这些数据不能用 response.text(那是给文本用的),要用 response.content

import requests

image_url = 'https://www.python.org/static/img/python-logo.png'

response = requests.get(image_url, timeout=10)
response.raise_for_status()

# 用 'wb' 模式写入二进制数据
with open('python_logo.png', 'wb') as file:
    file.write(response.content)

print('图片下载完成')

注意:写入文件时用 'wb' 模式(二进制写),不能用 'w'(那是文本写)。图片数据是二进制字节,不是可读的文字字符。

十一、爬虫礼仪:做一个负责任的爬虫

写爬虫是学技术,但用爬虫要讲规矩。几点基本的道德准则:

  • 遵守 robots.txt:在网站根目录(如 https://www.example.com/robots.txt)有一个文件,规定了哪些路径不允许爬虫访问。尊重它。
  • 不要高频请求:每次请求之间加一个间隔(time.sleep(1) 暂停一秒)。短时间内发送大量请求可能被视为攻击,导致你的 IP 被封。
  • 不要爬取版权内容:文章、图片、视频都有版权。爬取公开数据用于学习和研究一般没问题,但不要爬取付费内容或未经授权分发的数据。
  • 遵守网站的条款:有些网站在用户协议中明确禁止爬虫。如果网站有 API 接口,优先使用 API 而不是爬取网页。

十二、综合演示:完整的爬虫脚本

下面这个脚本综合运用了本篇学到的所有知识——发送请求、解析 HTML、处理翻页、伪装浏览器、异常处理、数据保存。它把抓取到的书籍信息保存为 JSON 文件:

import requests
from bs4 import BeautifulSoup
import json
import time

def scrape_books(max_pages=3):
    """抓取书籍信息,返回书籍列表"""
    base_url = 'https://books.toscrape.com/catalogue/'
    current_url = base_url + 'page-1.html'
    all_books = []

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }

    page = 0
    while current_url and page < max_pages:
        page += 1
        print(f'正在抓取第 {page} 页...')

        try:
            response = requests.get(current_url, headers=headers, timeout=10)
            response.raise_for_status()
        except requests.exceptions.RequestException as e:
            print(f'请求失败:{e}')
            break

        soup = BeautifulSoup(response.text, 'lxml')
        books = soup.select('.product_pod')

        for book in books:
            title = book.select_one('img')['alt']
            price = book.select_one('.price_color').text
            # 获取评分(class 属性包含 'star-rating' 的 p 标签)
            rating_tag = book.select_one('p[class^="star-rating"]')
            rating = rating_tag['class'][1] if rating_tag else '未知'

            all_books.append({
                'title': title,
                'price': price,
                'rating': rating
            })

        # 找下一页
        next_button = soup.select_one('.next > a')
        if next_button:
            current_url = base_url + next_button['href']
            time.sleep(1)  # 礼貌等待一秒
        else:
            current_url = None

    return all_books

# 运行爬虫
if __name__ == '__main__':
    books = scrape_books(max_pages=3)
    print(f'\n共抓取 {len(books)} 本书')

    # 保存为 JSON
    with open('books.json', 'w', encoding='utf-8') as file:
        json.dump(books, file, ensure_ascii=False, indent=2)

    print('数据已保存到 books.json')

    # 显示前 5 本
    print('\n前 5 本书:')
    for book in books[:5]:
        print(f'  《{book["title"]}》 - {book["price"]} - {book["rating"]}星')

十三、本篇动手练习

练习 1:抓取新闻标题

找一个你经常看的新闻网站首页,用 requests + BeautifulSoup 抓取页面上所有的新闻标题和链接。打印出来,把结果保存为 CSV 文件。

练习 2:抓取天气数据

搜索“城市名 天气”,找一个天气网站,查看页面的 HTML 结构。用爬虫抓取当天天气(温度、湿度、风力等),打印出来。

练习 3:批量下载图片

找一个包含多张图片的网页(比如壁纸网站或摄影网站),用爬虫获取所有图片的 URL,然后批量下载到本地文件夹。提示:图片 URL 通常在 <img> 标签的 src 属性中。

练习 4:模拟搜索

找一个搜索引擎(或用之前学过的示例网站),用 POST 请求模拟搜索——提交关键词,解析搜索结果页面,打印搜索到的结果。

十四、本篇小结

这一篇你学会了 Python 爬虫的核心技能:

  • 爬虫的本质:程序模拟浏览器发送 HTTP 请求,解析服务器返回的 HTML,提取数据。
  • requests:发送 GET/POST 请求、获取响应文本和二进制内容、处理异常。必须设置 timeout
  • BeautifulSoup:解析 HTML,用 find()find_all()select() 提取元素,访问标签属性和文本内容。前端开发者最熟悉 select() 配合 CSS 选择器。
  • 处理翻页:通过“下一页”按钮的链接自动拼接 URL,用 while 循环连续抓取。
  • 伪装浏览器:设置 User-Agent 请求头,让爬虫看起来像普通浏览器。
  • 下载文件:用 response.content 获取二进制数据,用 'wb' 模式写入文件。
  • 爬虫礼仪:控制请求频率、遵守 robots.txt、不爬取版权内容。

爬虫是 Python 的杀手锏技能。从这一篇开始,你已经能用 Python 做那些 JavaScript 做起来不太方便的事情了——自动抓取数据、批量下载文件、监控网页变化。下一篇,我们进入数据分析领域,学习用 Pandas 处理表格数据。

下一篇预告

下一篇——《数据分析入门——用 Pandas 处理表格数据》:安装 Pandas、读取 CSV/Excel 文件、数据筛选和排序、分组统计、合并多个数据源、简单的数据可视化。这是 Python 在数据科学领域最强大的能力之一。

Python 零基础入门,每周更新。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容