Python爬虫开发:将数据存入NoSQL(MongoDB)

一、引言

在当今互联网时代,海量数据充斥于各类平台和网站中。然而,这些数据往往以网页的形式存在,无法直接用于分析或存储。Python 爬虫是一种非常流行的技术,它能够模拟用户访问网站,抓取网页中的数据。然而,仅仅抓取到数据是不够的,我们还需要高效地存储和管理这些数据,以便后续分析和应用。

为什么选择 NoSQL(以 MongoDB 为例)?

与传统的关系型数据库(如 MySQL)相比,NoSQL 数据库(如 MongoDB)更适合与爬虫结合,其优势主要体现在以下几点:
  1. 数据格式灵活
    爬取到的数据往往结构不一,甚至包含嵌套结构。MongoDB 使用 JSON 格式存储数据,这种灵活的文档结构非常适合直接存储爬虫抓取到的半结构化或非结构化数据。
  2. 无需预先设计表结构
    爬取的数据可能动态变化,如新增字段或字段缺失。MongoDB 无需固定表结构(Schema-less),可以随时调整数据模型,避免了频繁修改数据库结构的麻烦。
  3. 高效的读写性能
    爬虫通常一次抓取大量数据,MongoDB 的高写入性能可以快速存储这些数据,并支持后续高效查询。
  4. 天然支持分布式
    在爬虫规模扩大时,MongoDB 的分布式特性可以轻松应对海量数据存储需求。
以下是爬虫与 MongoDB 结合的整体流程:
  1. 数据抓取
    使用 Python 的爬虫工具(如 requests)从目标网站获取网页内容。
  2. 数据解析
    使用 HTML 解析工具(如 BeautifulSoup 或 lxml)提取网页中的结构化数据,例如表格、列表等信息。
  3. 数据清洗与转换
    对抓取到的数据进行必要的清洗和格式化,转换为适合存储的字典(JSON)格式。
  4. 数据存储
    使用 MongoDB 的 Python 客户端(pymongo),将数据存储到数据库中。

    • 创建或选择数据库与集合。
    • 以文档的形式将数据插入 MongoDB。
  5. 后续分析与应用
    • 查询和提取存储的数据,支持业务逻辑。
    • 对数据进行进一步分析和可视化,或作为训练机器学习模型的基础数据。

案例背景

为了更好地理解这一流程,我们将以豆瓣电影 Top250 为例,爬取电影的标题、评分和简介,并将其存储到 MongoDB 中。通过这个案例,我们将实践整个从爬取到存储的过程,展示爬虫和 NoSQL 的完美结合如何为数据分析提供坚实的基础。

二、环境准备

安装依赖确保你安装了以下 Python 包:
pip install requests beautifulsoup4 pymongo
MongoDB 环境
  • 本地安装并启动 MongoDB 服务。
  • 或者使用云端 MongoDB 服务(如 MongoDB Atlas)。

三、实战案例:抓取豆瓣电影 Top250

抓取 豆瓣电影 Top250 的电影标题、评分和简介,并将数据存入 MongoDB,完成从数据获取到存储的完整流程。


1. 项目目标与分解

  • 项目目标
    通过爬虫技术抓取豆瓣电影 Top250 的电影数据,并利用 MongoDB 实现数据存储。
  • 任务分解
    1. 发送请求获取 HTML 数据。
    2. 解析 HTML 页面提取目标数据。
    3. 将提取的数据存储到 MongoDB 中。
    4. 爬取多页数据,完成 Top250 的数据收集。

2. 实现步骤

第一步:连接 MongoDB
通过 pymongo 连接数据库并创建集合:
from pymongo import MongoClient
# 配置 MongoDB 连接client = MongoClient("mongodb://localhost:27017/")db = client["douban"]  # 选择数据库collection = db["movies"]  # 选择集合
解释
  • MongoClient:连接 MongoDB 服务,可以指定本地地址或云端地址。
  • db["douban"]:选择名为 douban 的数据库(如果不存在会自动创建)。
  • collection:指定名为 movies 的集合,存储爬取的数据。

第二步:发送请求获取 HTML 数据
我们需要发送请求访问豆瓣 Top250 的网页,并获取页面的 HTML 数据:
import requests
# 爬取豆瓣电影 Top250def fetch_movies():    base_url = "https://movie.douban.com/top250"    headers = {"User-Agent": "Mozilla/5.0"}  # 模拟浏览器请求    for start in range(0, 250, 25):  # 每页显示 25 部电影        url = f"{base_url}?start={start}"        response = requests.get(url, headers=headers)        if response.status_code == 200:            parse_and_store(response.text)  # 解析并存储数据        else:            print(f"Failed to fetch page {start // 25 + 1}")
解释
  • requests.get(url, headers=headers):向目标网页发送 GET 请求。
  • start 参数控制分页,豆瓣电影每页显示 25 部电影,需循环获取 10 页数据。
  • parse_and_store(response.text):将获取的 HTML 传递到下一步解析函数处理。

第三步:解析 HTML 页面提取目标数据
解析 HTML 页面需要用到 BeautifulSoup,从中提取标题、评分和简介:
from bs4 import BeautifulSoup
# 解析 HTML 并存储到 MongoDBdef parse_and_store(html):    soup = BeautifulSoup(html, "html.parser")    items = soup.find_all("div", class_="item")  # 获取每部电影的条目
    for item in items:        movie = {            "title": item.find("span", class_="title").text,  # 电影标题            "rating": item.find("span", class_="rating_num").text,  # 评分            "summary": item.find("p", class_="").text.strip() if item.find("p", class_="") else "N/A",  # 简介        }        collection.insert_one(movie)  # 插入 MongoDB        print(f"Inserted: {movie['title']}")
解析过程
使用 BeautifulSoup 解析 HTML,指定 html.parser 作为解析器。
使用 find_all 查找所有 class="item" 的 div 标签(每部电影的数据条目)。
对每个条目提取以下数据:
标题find("span", class_="title").text 提取电影名称。
评分find("span", class_="rating_num").text 获取评分数值。
简介find("p", class_="").text.strip() 获取简介文本,处理缺失情况返回 "N/A"。
使用 collection.insert_one(movie) 将数据存储到 MongoDB。

第四步:运行主程序
整合上述步骤,运行主程序完成数据爬取与存储:
if __name__ == "__main__":    fetch_movies()    print("Data fetch complete!")

3. 验证结果

在命令行查看数据插入状态
运行程序后,终端会打印每部电影成功插入的信息,例如:
Inserted: 肖申克的救赎Inserted: 阿甘正传Inserted: 楚门的世界
在 MongoDB 中验证数据
使用 MongoDB Shell 或可视化工具(如 Robo 3T)查看存储的数据:
mongouse doubandb.movies.find().pretty()
示例输出
{    "_id": ObjectId("64b6f84a2b8e2c0012345678"),    "title": "肖申克的救赎",    "rating": "9.7",    "summary": "希望让人自由。"}

4. 优化与扩展

数据批量插入

如果需要提高存储效率,可以改用批量插入方式,将所有电影数据收集到一个列表中,最后统一存储:

def parse_and_store(html):    soup = BeautifulSoup(html, "html.parser")    items = soup.find_all("div", class_="item")    movies = []
    for item in items:        movies.append({            "title": item.find("span", class_="title").text,            "rating": item.find("span", class_="rating_num").text,            "summary": item.find("p", class_="").text.strip() if item.find("p", class_="") else "N/A",        })
    collection.insert_many(movies)  # 批量插入    print(f"Inserted {len(movies)} movies")
添加字段与存储时间
在存储数据时,可以添加字段,如存储时间戳便于后续分析:
from datetime import datetime
movie = {    "title": "肖申克的救赎",    "rating": "9.7",    "summary": "希望让人自由。",    "timestamp": datetime.now()  # 添加存储时间}collection.insert_one(movie)
以下是抓取 豆瓣电影 Top250 数据并存入 MongoDB 的完整代码,包含所有步骤和注释:
import requestsfrom bs4 import BeautifulSoupfrom pymongo import MongoClientfrom datetime import datetime
# Step 1: 配置 MongoDB 连接client = MongoClient("mongodb://localhost:27017/")db = client["douban"]  # 数据库名:doubancollection = db["movies"]  # 集合名:movies
# Step 2: 爬取数据def fetch_movies():    """    爬取豆瓣电影 Top250 数据并存储到 MongoDB    """    base_url = "https://movie.douban.com/top250"    headers = {"User-Agent": "Mozilla/5.0"}  # 模拟浏览器请求头    for start in range(0, 250, 25):  # 分页抓取,每页 25 部电影        url = f"{base_url}?start={start}"        print(f"Fetching page {start // 25 + 1}...")        response = requests.get(url, headers=headers)
        if response.status_code == 200:            parse_and_store(response.text)  # 解析并存储数据        else:            print(f"Failed to fetch page {start // 25 + 1}. Status code: {response.status_code}")
# Step 3: 解析数据并存储到 MongoDBdef parse_and_store(html):    """    解析 HTML 数据并存储到 MongoDB    """    soup = BeautifulSoup(html, "html.parser")    items = soup.find_all("div", class_="item")  # 获取每部电影的条目
    movies = []    for item in items:        movie = {            "title": item.find("span", class_="title").text,  # 电影标题            "rating": item.find("span", class_="rating_num").text,  # 评分            "summary": item.find("p", class_="").text.strip() if item.find("p", class_="") else "N/A",  # 简介            "timestamp": datetime.now()  # 存储时间        }        movies.append(movie)
    # 批量插入到 MongoDB    if movies:        collection.insert_many(movies)        print(f"Inserted {len(movies)} movies.")
# Step 4: 主程序入口if __name__ == "__main__":    fetch_movies()    print("Data fetch complete!")

四、批量插入的详细描述

1. 什么是批量插入?

批量插入是指将多条数据一次性写入数据库,而不是逐条插入。这种方法能够显著提高存储效率,特别是在需要存储大量数据的场景中。

对于 MongoDB,单条插入使用 insert_one() 方法,而批量插入则使用 insert_many() 方法。后者可以将一个包含多条文档的列表一次性插入到数据库中,减少网络开销和 MongoDB 的请求处理时间。


2. 为什么选择批量插入?

  • 性能提升:每调用一次 insert_one(),程序需要与 MongoDB 服务交互一次。当数据量较大时,频繁的网络交互会导致性能下降。而 insert_many() 只需与数据库交互一次即可插入多条数据。
  • 降低系统资源消耗:批量插入减少了 MongoDB 的 I/O 操作,使服务器可以更高效地处理其他请求。
  • 便于管理:将一组相关数据作为一个整体操作,代码逻辑更清晰。

3. 如何实现批量插入?

以下是将爬取的多部电影数据一次性插入到 MongoDB 的代码实现:

代码示例:
def parse_and_store(html):    from bs4 import BeautifulSoup
    # 使用 BeautifulSoup 解析 HTML    soup = BeautifulSoup(html, "html.parser")    items = soup.find_all("div", class_="item")
    # 临时存储电影数据的列表    movies = []
    for item in items:        # 逐一提取每部电影的信息        movies.append({            "title": item.find("span", class_="title").text,            "rating": item.find("span", class_="rating_num").text,            "summary": item.find("p", class_="").text.strip() if item.find("p", class_="") else "N/A",        })
    # 使用 insert_many 批量插入    if movies:  # 确保列表非空        collection.insert_many(movies)        print(f"Inserted {len(movies)} movies.")

4. 代码逻辑拆解

创建数据列表
将每条解析出来的电影数据以字典形式存入 movies 列表中,避免逐条插入:
movies.append({    "title": ...,    "rating": ...,    "summary": ...})
批量插入
使用 insert_many() 方法将整个列表插入到 MongoDB 中:
collection.insert_many(movies)

插入检查

在调用 insert_many() 之前,确认数据列表 movies 非空,避免因空列表导致异常。
if movies:    collection.insert_many(movies)

5. 批量插入的优势对比

操作方式 优点 缺点
单条插入 (insert_one) 适合少量数据,操作简单 多次调用效率低,耗费资源
批量插入 (insert_many) 高效插入大量数据,减少网络和 I/O 开销 需要提前准备所有数据列表

来源:海南练习生 数据挖掘练习生

THE END