一、 前言:什么是网络爬虫?

网络爬虫(Web Crawler) 是一种自动浏览互联网并收集数据的程序。它们被广泛应用于搜索引擎、数据挖掘、价格监测、学术研究等领域。

一个基本的爬虫工作流程包括:

  1. 发送HTTP请求:获取网页内容。
  2. 解析内容:从HTML、JSON或其他格式中提取所需数据。
  3. 存储数据:将提取的数据保存到文件或数据库。
  4. 跟踪链接(可选):根据策略继续访问其他页面。

Python在爬虫领域有着丰富的生态系统,其中requests用于发送HTTP请求,BeautifulSoup用于解析HTML,是初学者最常用的组合。


二、 爬虫的伦理与法律问题

在编写爬虫之前,必须了解相关的伦理和法律问题:

  1. 尊重robots.txt:该文件定义了爬虫被允许和禁止访问的页面。使用robotparser模块可以解析此文件。
  2. 限制请求频率:过于频繁的请求可能被视为攻击,导致IP被封锁。应添加延迟或使用代理轮换。
  3. 识别自己:通过User-Agent头部标识爬虫,提供联系方式以便网站管理员联系。
  4. 遵守版权法:抓取的数据可能受版权保护,未经许可不得用于商业目的。
  5. 不抓取个人信息:避免抓取用户个人信息,除非获得明确授权。

重要:在抓取任何网站之前,请确保你有权这样做,并遵守网站的使用条款。


三、 环境设置

首先,安装必要的库:

pip install requests beautifulsoup4 lxml
  • requests:发送HTTP请求。
  • beautifulsoup4:解析HTML和XML。
  • lxml:BeautifulSoup的解析器,比标准库解析器更快更强大。

四、 使用requests获取网页内容

requests库使HTTP请求变得非常简单。以下是一个基本示例:

import requests

url = 'https://example.com'

# 发送GET请求
response = requests.get(url)

# 检查请求是否成功
if response.status_code == 200:
    print('成功获取网页内容')
    # 响应内容(HTML)
    html_content = response.text
    print(html_content[:1000])  # 打印前1000个字符
else:
    print(f'请求失败,状态码: {response.status_code}')

关键点:

  • response.status_code:HTTP状态码,200表示成功。
  • response.text:响应内容的字符串形式(自动解码)。
  • response.content:原始字节内容,用于非文本内容(如图片)。

设置请求头:有些网站会检查User-Agent,如果没有设置,可能会拒绝请求。我们可以模拟浏览器:

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}

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

处理异常:网络请求可能会失败,添加异常处理使程序更健壮:

try:
    response = requests.get(url, headers=headers, timeout=5)
    response.raise_for_status()  # 如果状态码不是200,抛出HTTPError异常
except requests.exceptions.RequestException as e:
    print(f'请求出错: {e}')

五、 使用BeautifulSoup解析HTML

获得HTML内容后,我们需要从中提取数据。BeautifulSoup将复杂的HTML转换为树形结构,便于遍历和搜索。

1. 创建BeautifulSoup对象
from bs4 import BeautifulSoup

# 使用lxml解析器创建BeautifulSoup对象
soup = BeautifulSoup(html_content, 'lxml')

#  prettify()方法可以格式化输出HTML
print(soup.prettify()[:1000])
2. 查找元素

BeautifulSoup提供了多种查找方法,最常用的是find()find_all()

  • 按标签名查找
# 查找第一个 <title> 标签
title_tag = soup.find('title')
print(title_tag.text)  # 输出标签的文本内容

# 查找所有 <a> 标签
all_links = soup.find_all('a')
for link in all_links:
    print(link.get('href'))  # 获取href属性
  • 按属性查找
# 查找具有特定class的div
divs = soup.find_all('div', class_='some-class')  # 注意class_,因为class是Python关键字

# 查找具有特定id的元素
element = soup.find(id='main-content')

# 使用字典指定多个属性
element = soup.find('a', {'href': 'https://example.com', 'class': 'external'})
  • 使用CSS选择器select()方法支持CSS选择器语法。
# 选择所有具有class为"article"的div下的所有a标签
links = soup.select('div.article a')

for link in links:
    print(link.get('href'))
3. 提取数据
  • 文本内容:使用.text.get_text()
  • 属性值:使用.get('attr_name')
# 假设我们有一个这样的HTML片段:
# <div class="product">
#   <h2>产品名称</h2>
#   <p class="price">¥100</p>
#   <a href="/product/1">链接</a>
# </div>

product_div = soup.find('div', class_='product')
name = product_div.h2.text
price = product_div.find('p', class_='price').text
link = product_div.a.get('href')

print(f'名称: {name}, 价格: {price}, 链接: {link}')

六、 实战项目:爬取图书信息

让我们爬取一个示例网站(http://books.toscrape.com/)的图书信息。该网站专门用于练习爬虫。

目标:获取每本书的标题、价格、评分和库存状态。

import requests
from bs4 import BeautifulSoup
import csv
import time

def scrape_books():
    base_url = 'http://books.toscrape.com/'
    response = requests.get(base_url)
    soup = BeautifulSoup(response.text, 'lxml')
    
    # 查找所有图书 article 标签
    books = soup.find_all('article', class_='product_pod')
    
    book_data = []
    for book in books:
        # 提取标题
        title = book.h3.a['title']
        
        # 提取价格
        price = book.find('p', class_='price_color').text
        # 去掉英镑符号并转换为浮点数
        price = float(price[1:])
        
        # 提取评分
        rating_class = book.find('p', class_='star-rating')['class']
        # 评分在class的第二个元素,例如 'star-rating Three' -> 'Three'
        rating = rating_class[1]
        # 将评分转换为数字
        rating_map = {'One': 1, 'Two': 2, 'Three': 3, 'Four': 4, 'Five': 5}
        rating = rating_map.get(rating, 0)
        
        # 提取库存状态
        availability = book.find('p', class_='instock availability').text.strip()
        
        book_data.append({
            'title': title,
            'price': price,
            'rating': rating,
            'availability': availability
        })
    
    return book_data

def save_to_csv(book_data, filename='books.csv'):
    with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
        fieldnames = ['title', 'price', 'rating', 'availability']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        
        writer.writeheader()
        for book in book_data:
            writer.writerow(book)

if __name__ == '__main__':
    print('开始爬取图书信息...')
    book_data = scrape_books()
    save_to_csv(book_data)
    print(f'共爬取 {len(book_data)} 本书的信息,已保存到 books.csv')

代码解析

  1. 我们发送请求到首页并解析HTML。
  2. 每本书的信息包裹在<article class="product_pod">中。
  3. 我们遍历所有这样的article元素,从中提取所需信息。
  4. 将提取的数据保存到CSV文件中。

七、 处理分页

上面的例子只爬取了第一页。要爬取所有页,我们需要发现分页链接并依次访问。

def scrape_all_pages():
    base_url = 'http://books.toscrape.com/'
    current_url = base_url
    all_books = []
    
    while current_url:
        print(f'正在爬取: {current_url}')
        response = requests.get(current_url)
        soup = BeautifulSoup(response.text, 'lxml')
        
        # 爬取当前页的图书
        books = soup.find_all('article', class_='product_pod')
        for book in books:
            # ... 提取代码同上 ...
            all_books.append(book_info)
        
        # 查找下一页链接
        next_button = soup.find('li', class_='next')
        if next_button:
            next_link = next_button.a['href']
            # 构建绝对URL
            if 'catalogue' in next_link:
                current_url = base_url + next_link
            else:
                current_url = base_url + 'catalogue/' + next_link
        else:
            current_url = None
        
        # 延迟一下,避免请求过于频繁
        time.sleep(1)
    
    return all_books

八、 应对反爬虫机制

网站可能会使用一些技术阻止爬虫:

  1. User-Agent检测:我们已经通过设置请求头应对。
  2. IP限制:使用代理IP轮换。
  3. 验证码:难以自动处理,可能需要人工干预或使用第三方验证码解决服务。
  4. JavaScript渲染:有些内容由JavaScript动态加载,requests无法获取。此时可使用Selenium或Playwright。

使用代理示例

proxies = {
    'http': 'http://10.10.1.10:3128',
    'https': 'http://10.10.1.10:1080',
}

response = requests.get(url, proxies=proxies, timeout=5)

随机延迟:避免固定间隔的请求,使用随机延迟更模拟人类行为。

import random

time.sleep(random.uniform(1, 3))

九、 总结与最佳实践
  1. 遵守规则:尊重robots.txt,控制请求频率。
  2. 健壮性:添加异常处理、重试机制。
  3. 效率:考虑使用多线程或异步提高爬取速度(但需谨慎,避免给服务器造成压力)。
  4. 持久化:及时保存已爬取的数据,避免重复工作。
  5. 维护性:网站结构可能变化,定期检查并更新爬虫。

通过本章,你学会了使用requests和BeautifulSoup构建基础爬虫。接下来,你可以学习更高级的框架(如Scrapy)以及如何处理JavaScript渲染的页面。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐