前几篇实战中,我们的数据均来自手动录入或本地生成。但在真实开发中,更多场景需要从网络自动抓取有价值的信息——比如行业资讯、公开榜单等。今天,我们将聚焦Python爬虫的基础实战,从核心概念、合规原则到完整项目开发,打通“数据获取→解析→存储”的全流程。本文不仅教你写代码,更帮你建立合规爬取的思维框架,为后续数据分析、Web开发等进阶方向打下坚实基础。

️ 一、爬虫核心认知:是什么、为什么、怎么做

在动手写代码前,先厘清爬虫的本质与边界。Python爬虫本质上是一段自动化程序,它模拟浏览器向目标网站发送HTTP请求,获取服务器返回的数据,再通过解析提取出有价值的信息,最终保存到本地文件或数据库中。其核心工作流程可概括为四步:

  • 发送请求:模拟浏览器向服务器发送GET/POST请求,携带必要的请求头(如User-Agent),获取响应数据(HTML源码、JSON等)。
  • 解析数据:对响应数据进行处理,提取所需内容(如文本、链接、图片地址),过滤无关信息。常用工具有BeautifulSoup4和正则表达式。
  • 数据存储:将结构化数据写入JSON、CSV文件或数据库,便于后续分析或使用。
  • 自动化控制:通过循环、分页等技术实现批量爬取,提升效率。

重要提示:爬虫开发必须遵守法律法规和平台规则。核心原则包括:遵守robots协议、控制爬取频率(使用time.sleep())、尊重版权与隐私、伪装请求头、不突破反爬机制。本次实战选用豆瓣公开榜单等无反爬限制的测试网站,仅用于学习目的。

⚙️ 二、核心技术拆解:请求发送与数据解析

爬虫的两大核心技能是“发送请求获取数据”和“解析提取数据”。我们先分别掌握它们,再结合实战整合应用。

1. 网络请求:requests模块基础用法

Python的requests库是爬虫中最常用的HTTP工具,支持GET/POST请求,能轻松处理请求头和响应数据。以下是GET请求的典型用法:

import requests
import time
# 1. 定义目标URL(以豆瓣电影Top250公开榜单为例,合规爬取)
url = "https://movie.douban.com/top250?start=0&filter="
# 2. 定义请求头,伪装成浏览器(从浏览器开发者工具中复制真实User-Agent)
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"
}
try:
    # 3. 发送GET请求,设置超时时间(避免无限等待)
    response = requests.get(url, headers=headers, timeout=10)
    # 4. 校验响应状态码(200表示请求成功)
    if response.status_code == 200:
        # 获取网页源码(文本形式)
        html = response.text
        print("请求成功,网页源码长度:", len(html))
    else:
        print(f"请求失败,状态码:{response.status_code}")
except requests.exceptions.RequestException as e:
    print(f"请求异常:{str(e)}")
# 控制爬取频率,间隔1秒
time.sleep(1)

响应对象提供多种数据获取方式:

  • response.text:获取文本形式的数据(适用于HTML)。
  • response.content:获取二进制数据(适用于图片、视频)。
  • response.json():自动解析JSON格式数据(适用于API接口)。
  • response.status_code:获取状态码,判断请求是否成功。

2. 数据解析:BeautifulSoup4与正则表达式

获取HTML源码后,需要提取有效信息。BeautifulSoup4(bs4) 通过标签、属性定位元素,简单直观;正则表达式 则适用于复杂文本匹配。新手推荐优先使用bs4:

from bs4 import BeautifulSoup
import requests
# 先发送请求获取网页源码(复用上面的请求逻辑)
url = "https://movie.douban.com/top250?start=0&filter="
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"}
try:
    response = requests.get(url, headers=headers, timeout=10)
    if response.status_code == 200:
        # 初始化bs4解析器(指定lxml解析器,需提前安装:pip install lxml)
        soup = BeautifulSoup(response.text, "lxml")
        # 1. 按标签+类名定位元素(提取电影列表项)
        movie_list = soup.find_all("div", class_="item")  # find_all返回所有匹配元素的列表
        print(f"共找到 {len(movie_list)} 部电影")
        # 2. 遍历电影列表,提取每部电影的信息
        for movie in movie_list:
            # 提取电影名称(定位span标签,class为title)
            name = movie.find("span", class_="title").get_text()  # get_text()获取标签内文本
            # 提取评分(定位span标签,class为rating_num)
            score = movie.find("span", class_="rating_num").get_text()
            # 提取简介(定位p标签,获取文本并处理)
            info = movie.find("p", class_="").get_text(strip=True).replace("\n", " ")
            # 提取详情链接
            link = movie.find("a")["href"]  # 获取a标签的href属性值
            # 打印提取的信息
            print(f"电影名称:{name}")
            print(f"评分:{score}")
            print(f"详情链接:{link}")
            print("-"*50)
except requests.exceptions.RequestException as e:
    print(f"请求异常:{str(e)}")

bs4核心方法:

  • soup.find(tag, attrs):查找第一个匹配元素。
  • soup.find_all(tag, attrs):查找所有匹配元素,返回列表。
  • element.get_text(strip=True):获取元素内文本。
  • element["attr_name"]:获取属性值(如href、src)。

当数据无法通过标签准确定位时,可结合正则表达式匹配文本模式:

import re
import requests
url = "https://movie.douban.com/top250?start=0&filter="
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"}
try:
    response = requests.get(url, headers=headers, timeout=10)
    if response.status_code == 200:
        # 定义正则表达式模式(匹配电影名称,需结合网页源码调整)
        pattern = re.compile(r'([^<]+)', re.S)
        # 查找所有匹配的结果
        names = re.findall(pattern, response.text)
        # 去重(过滤重复的电影名称标签)
        unique_names = list(set(names))
        print("提取的电影名称:")
        for idx, name in enumerate(unique_names, 1):
            print(f"{idx}. {name}")
except requests.exceptions.RequestException as e:
    print(f"请求异常:{str(e)}")

⚠️ 注意:正则表达式的核心是“精准匹配文本模式”,需结合网页源码结构调整表达式。建议先掌握bs4,再逐步过渡到正则表达式处理复杂场景。

三、实战案例:豆瓣电影Top250爬取(完整项目)

现在,我们将前面学到的请求发送、数据解析、持久化技术整合起来,开发一个完整的爬虫项目——爬取豆瓣电影Top250的全部电影信息(名称、评分、导演、详情链接),并存储到JSON与CSV文件中。项目严格遵循模块化开发思想。

1. 项目架构设计

按“请求模块→解析模块→存储模块→主程序”拆分代码,结构清晰、易于维护:

douban_movie_spider/  # 爬虫项目根目录
├── spider.py         # 核心爬虫模块:封装请求、解析逻辑
├── storage.py        # 存储模块:封装JSON、CSV文件持久化
├── main.py           # 主程序:调度爬虫与存储,控制整体流程
└── movies.json       # 存储爬取结果(自动生成)
└── movies.csv        # 存储爬取结果(自动生成)

2. 逐模块实现

(1)核心爬虫模块:spider.py

# spider.py:豆瓣电影Top250爬虫核心模块
import requests
from bs4 import BeautifulSoup
import time
import random
class DoubanMovieSpider:
    def __init__(self):
        self.base_url = "https://movie.douban.com/top250?start={}&filter="
        self.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-Language": "zh-CN,zh;q=0.9"  # 增加语言头,提升兼容性
        }
        self.movie_list = []  # 存储所有电影信息
    def send_request(self, url):
        """发送请求,获取网页源码"""
        try:
            # 随机设置请求间隔(1-3秒),模拟人工浏览,避免被封禁
            time.sleep(random.uniform(1, 3))
            response = requests.get(url, headers=self.headers, timeout=15)
            if response.status_code == 200:
                return response.text
            else:
                print(f"请求失败,状态码:{response.status_code},URL:{url}")
                return None
        except requests.exceptions.RequestException as e:
            print(f"请求异常:{str(e)},URL:{url}")
            return None
    def parse_html(self, html):
        """解析网页源码,提取电影信息"""
        if not html:
            return
        soup = BeautifulSoup(html, "lxml")
        movie_items = soup.find_all("div", class_="item")
        for item in movie_items:
            # 提取核心信息
            movie_info = {
                "name": item.find("span", class_="title").get_text(),  # 电影名称
                "score": item.find("span", class_="rating_num").get_text(),  # 评分
                "director": item.find("p", class_="").get_text(strip=True).split("\n")[0].split("导演: ")[1].split("主演: ")[0],  # 导演
                "link": item.find("a")["href"],  # 详情链接
                "quote": item.find("span", class_="inq").get_text() if item.find("span", class_="inq") else "无简介"  # 简介(可选)
            }
            self.movie_list.append(movie_info)
            print(f"已提取:{movie_info['name']}")
    def crawl(self):
        """批量爬取所有页面(Top250共10页,每页25条)"""
        print("开始爬取豆瓣电影Top250...")
        for page in range(10):
            start = page * 25  # 每页起始位置(0,25,50...225)
            url = self.base_url.format(start)
            print(f"正在爬取第{page+1}页,URL:{url}")
            html = self.send_request(url)
            self.parse_html(html)
        print(f"爬取完成,共提取 {len(self.movie_list)} 部电影信息")
        return self.movie_list

(2)存储模块:storage.py

# storage.py:数据存储模块(JSON+CSV)
import json
import csv
class DataStorage:
    @staticmethod
    def save_to_json(data, file_path="movies.json"):
        """将数据保存到JSON文件"""
        try:
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            print(f"数据已成功保存到JSON文件:{file_path}")
        except Exception as e:
            print(f"JSON文件保存失败:{str(e)}")
    @staticmethod
    def save_to_csv(data, file_path="movies.csv"):
        """将数据保存到CSV文件(更适合表格类数据)"""
        if not data:
            print("无数据可保存到CSV文件")
            return
        try:
            # 获取字段名(字典的键)
            fieldnames = data[0].keys()
            with open(file_path, "w", encoding="utf-8", newline="") as f:
                writer = csv.DictWriter(f, fieldnames=fieldnames)
                writer.writeheader()  # 写入表头
                writer.writerows(data)  # 写入数据
            print(f"数据已成功保存到CSV文件:{file_path}")
        except Exception as e:
            print(f"CSV文件保存失败:{str(e)}")

(3)主程序:main.py

# main.py:豆瓣电影爬虫主程序
from spider import DoubanMovieSpider
from storage import DataStorage
def main():
    # 初始化爬虫
    spider = DoubanMovieSpider()
    # 开始爬取
    movie_data = spider.crawl()
    # 保存数据(JSON+CSV双格式)
    if movie_data:
        DataStorage.save_to_json(movie_data)
        DataStorage.save_to_csv(movie_data)
    print("爬虫程序执行完毕!")
if __name__ == "__main__":
    main()

3. 运行与验证

运行main.py,程序将自动爬取10页数据,过程中打印每部电影的名称,最终生成movies.jsonmovies.csv文件。验证要点:

  • ✅ JSON文件:结构清晰,包含名称、评分、导演等信息,无乱码。
  • ✅ CSV文件:表头与数据对应,可用Excel直接打开分析。
  • ✅ 爬取过程:无请求异常提示,爬取频率合理(已设置间隔),未被封禁IP。

️ 四、常见问题与排查技巧

爬虫开发中难免遇到各种问题,以下总结高频问题及解决方案:

  • 请求被拒绝(状态码403/404):未设置请求头、频率过高、IP被封。解决方案:添加完整请求头(User-Agent、Referer),增加请求间隔,必要时更换IP。
  • 解析失败(提取不到数据):网页结构变化、解析表达式错误、编码问题。解决方案:用F12开发者工具重新查看源码,调整解析逻辑;设置response.encoding="utf-8"
  • 请求超时/连接错误:网络不稳定、服务器响应慢。解决方案:设置更长的超时时间,检查网络连接。
  • 被网站封禁IP:频繁请求、未伪装浏览器。解决方案:停止爬取一段时间,优化请求头与频率,严格遵守合规原则。

[AFFILIATE_SLOT_1]

五、Python基础阶段知识体系总结

至此,Python基础阶段的全部核心内容已讲解完毕。从基础语法到三大实战项目,我们构建了完整的知识与技能体系。以下是核心知识总结:

1. 核心语法模块

  • 数据类型:整数、浮点数、字符串、列表、字典、元组、集合的特性与常用方法。
  • 流程控制:if-elif-else条件判断、for/while循环,结合break、continue控制流程。
  • 函数编程:函数定义、参数传递(位置参数、关键字参数、默认参数)、返回值,理解封装思想。

2. 进阶核心模块

  • 面向对象编程:类与对象、封装、初始化方法,适用于复杂项目。
  • 异常处理:try-except捕获异常,raise主动抛出异常,确保程序稳定。
  • 模块化与包:按功能拆分模块,理解“高内聚、低耦合”原则。
  • 文件持久化:JSON、CSV、文本文件的读写方法,实现数据长期存储。

3. 实战项目模块

  • 猜数字游戏:巩固基础语法与异常处理,建立项目思维。
  • 简易通讯录:掌握面向对象封装与JSON持久化,学会分层开发。
  • 爬虫入门:打通“数据获取→解析→存储”全流程,掌握合规爬取要点。

4. 核心思维总结

基础阶段不仅是学习语法,更重要的是培养正确的开发思维:封装思维(提升复用性)、模块化思维(降低耦合)、异常防护思维(保证稳定)、合规思维(坚守技术底线)。这些思维同样适用于Java、TypeScript、C++、Go等语言的学习,是程序员通用的核心素养。

[AFFILIATE_SLOT_2]

六、全文总结

Python基础阶段的学习之旅至此圆满结束。从一行行简单的打印语句,到能独立开发功能完整的实战项目,你不仅掌握了Python的核心语法与技能,更建立了系统化的开发思维与问题处理能力。这些知识是后续学习数据分析、Web开发、人工智能等进阶方向的坚实基础。记住:编程学习没有捷径,唯有“理解+实践+复盘”才能真正掌握。基础阶段的结束,正是进阶学习的开始。愿你在Python的世界里继续深耕,写出更优秀的代码,实现更多有趣的想法!