网络机器人---概述

网络机器人:就是爬虫,是一种按照约定预设规则,自动浏览并抓取网络数据的程序或脚本。

数据清洗:是指对采集到的原始数据进行处理,修正,转换和标准化的过程,目的是让数据更加规范,准确。

网络机器人---合规性(robots协议)

robots协议:又称爬虫协议、爬虫规则,是指在网页根目录下放置一份文本文件robots.txt,用于告知爬虫哪些页面可以抓取,哪些页面不能抓取(君子协议)

一份robots协议允许定制多份规则

网站域名+/+robots.txt可以查看该网站的robots协议

/所有资源

*代表的事任何网络机器人

user-Agent :用户代理,通过该请求头确认爬虫的类型

Dissalow     :禁止访问的资源

Allow           :允许访问的资源

Sitemap      :网站地图,方便爬虫更高效的获取网站的内容

Crawl-delay:抓取间隔时间,避免频繁访问造成网络压力过大

前端网页结构

一个网页由三个部分组成:HTML , CSS , JS(JavaScript)

HTML :超文本标记语言,由一堆预设的标签(<h1>一级标题<h1>)构成,HTML负责网页的结构(页面元素和内容)

CSS   :层叠样式表。CSS负责网页的外观(页面元素的位置外观,颜色,大小字体的颜色大小)

JS      : 负责网页的行为(交互效果)

HTML是骨架,CSS是皮囊,JS是灵魂

网络解析-入门

lxml :是一个高性能的HTML/XML文档的解析库,支持基于Xpath语法来解析和获取的网页数据

Xpath: 是一种用于在HTML/XML文档中的导航或定位元素的查询语言,让你能够精准的查询到文档中的定位元素、属性或文本。

安装方法:pip install lxml

导入方法:from lxml import html

from lxml import html
import os

# 读取同目录下的html文件
html_path = os.path.join(os.path.dirname(__file__), 'resources', '仙逆人物志.html')

with open(html_path, 'r', encoding='utf-8') as f:
    html_text = f.read()
    #print(html_text)
    #解析html文本,将其转化为一个文档
    document = html.fromstring(html_text)

    # 解析表头  - Xpath语法
    th_list = document.xpath('//table/thead/tr/th/text()')
    print(th_list)

    #遍历tr
    td_list = document.xpath('//table/tbody/tr[1]/td/text()')
    print(td_list)

    #遍历tr中的每一个
    tr_list = document.xpath('//table/tbody/tr')
    for tr in tr_list :
        td_list = tr.xpath('./td/text()')
        print(td_list)

Xpath语法总结

爬取电影网站数据实例

CSV

CSV(Comma-Separted Values,逗号分隔值):是一种简单、通用的文本格式,用于储存表格数据,可以直接用Excel打开。(要用英文逗号,csv后缀,系统编码保存)

import os
import csv

script_dir = os.path.dirname(os.path.abspath(__file__))
csv_dir = os.path.join(script_dir, "csv_data")
os.makedirs(csv_dir, exist_ok=True)

#csv操作1: 创建csv文件
# with open(os.path.join(csv_dir, "01.csv"), "w", encoding="utf-8", newline='') as f:
#     f.write("姓名,性别,年龄,爱好\n")
#     f.write("小王,男,18,'football,java'\n")
#     f.write("小李,女,19,basketball\n")
#     f.write("小张,男,20,swimming\n")
#     f.write("小王,女,21,runa\n")

# 读取
# with open(os.path.join(csv_dir, "01.csv"), "r", encoding="utf-8") as f:
#     for line in f:
#         print(line.strip())

#csv操作2:
#写入
with open(os.path.join(csv_dir, "02.csv"), "w", encoding="utf-8", newline='') as f:
    writer = csv.DictWriter(f, fieldnames=["姓名","性别","年龄","爱好"])
    writer.writeheader()
    writer.writerow({"姓名":"小王","性别":"男","年龄":"18","爱好":"football,java"})
    writer.writerow({"姓名":"小李","性别":"女","年龄":"19","爱好":"basketball"})
    writer.writerow({"姓名":"小张","性别":"男","年龄":"20","爱好":"swimming"})
    writer.writerow({"姓名":"小王","性别":"女","年龄":"21","爱好":"ran"})

#读取
with open(os.path.join(csv_dir, "02.csv"), "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row)

代码总结

import requests
import csv
from lxml import html


#常量
MOVIE_LIST_FILE = 'csv_data/movies_list.csv'
TMDB_BASE_URL = 'https://www.themoviedb.org'
TMDB_TOP_URL  = 'https://www.themoviedb.org/movie/top-rated'
#主函数,定义核心逻辑

#保存电影数据
def save_all_movies(all_movies):
    with open(MOVIE_LIST_FILE, 'w', encoding='utf-8', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=["电影名", "上映年份", "上映日期", "类型", "时长", "评分", "语言", "导演", "作者", "宣传语", "简介"])
        writer.writeheader()
        writer.writerows(all_movies)

#获取电影详情
def get_movie_info(movie_url):


    #发送请求,获取电影详情数据
    movie_response = requests.get(movie_url,timeout = 60)
    print(f"发送请求{movie_url},获取电影详情数据成功")
    #解析数据,获取电影详情
    movie_doc = html.fromstring(movie_response.text)
    names = movie_doc.xpath('//meta[@property="og:title"]/@content')[0]
    years = movie_doc.xpath('//span[contains(@class, "release_date")]/text()')
    dates = movie_doc.xpath('//span[@class="release"]/text()')
    tags = movie_doc.xpath('//a[contains(@href, "/genre/")]/text()')
    cost_times = movie_doc.xpath('//span[@class="runtime"]/text()')
    scores = movie_doc.xpath('//div[contains(@class, "user_score")]/@data-percent')
    lang_ps = movie_doc.xpath('//section[contains(@class, "facts")]//p[contains(., "默认语言")]')

    # 导演、作者
    crew_names = movie_doc.xpath('//li[contains(@class, "profile")]//a/text()')
    crew_roles = movie_doc.xpath('//li[contains(@class, "profile")]//p/text()')
    directors = [crew_names[i] for i, r in enumerate(crew_roles) if 'Director' in r]
    writers = [crew_names[i] for i, r in enumerate(crew_roles) if any(w in r for w in ['Screenplay', 'Novel', 'Writer'])]

    # 宣传语
    taglines = movie_doc.xpath('//*[contains(@class, "tagline")]/text()')

    # 简介
    overviews = movie_doc.xpath('//meta[@property="og:description"]/@content')

    movie_name = names
    movie_year = years[0].strip("()") if years else ""
    movie_date = dates[0].strip() if dates else ""
    movie_tags = tags
    movie_runtime = cost_times[0].strip() if cost_times else ""
    movie_score = scores[0] if scores else ""
    movie_language = lang_ps[0].xpath('string()').replace("默认语言", "").strip() if lang_ps else ""
    movie_director = directors[0] if directors else ""
    movie_writer = writers[0] if writers else ""
    movie_tagline = taglines[0].strip() if taglines else ""
    movie_overview = overviews[0] if overviews else ""

    # print(movie_name)
    # print(f"  年份: {movie_year}")
    # print(f"  上映日期: {movie_date}")
    # print(f"  类型: {movie_tags}")
    # print(f"  时长: {movie_runtime}")
    # print(f"  评分: {movie_score}%")
    # print(f"  语言: {movie_language}")
    # print(f"  导演: {movie_director}")
    # print(f"  作者: {movie_writer}")
    # print(f"  宣传语: {movie_tagline}")
    # print(f"  简介: {movie_overview[:80]}...")
    
    #3、返回电影详情
    movie_info = {
        "电影名": movie_name,
        "上映年份": movie_year,
        "上映日期": movie_date,
        "类型": ",".join(movie_tags) if movie_tags else "",
        "时长": movie_runtime,
        "评分": movie_score,
        "语言": movie_language,
        "导演": movie_director,
        "作者": movie_writer,
        "宣传语": movie_tagline,
        "简介": movie_overview
    }
    # print(movie_info)
    return movie_info


def main():
    #1、发送请求,获得电影榜单数据
    response = requests.get(TMDB_TOP_URL,timeout = 60)


    #2、解析数据,获取电影列表
    document = html.fromstring(response.text)
    movie_list = document.xpath('//div[contains(@class, "poster-card")]')

    #3、遍历电影列表,获取电影详情
    all_movies = []
    for movie in movie_list:
        movie_urls = movie.xpath(".//a[contains(@href, '/movie/')]/@href")
        if movie_urls:
            movie_info_url = TMDB_BASE_URL + movie_urls[0]

            #发送请求,获取电影详情数据
            movie_info = get_movie_info(movie_info_url)
            all_movies.append(movie_info)

    #4、保存数据,写入csv文件 
    print("保存数据中...")
    save_all_movies(all_movies)

if __name__ == '__main__':
    main()

代码解析

# 🛠️ 环境准备
# 在开始之前,请确保你的Python环境中安装了以下第三方库:
# pip install requests lxml
# 注:csv 和 os 是Python内置库,无需额外安装。
#
# 🧠 核心逻辑拆解
# 整个爬虫项目的流程可以分为四个标准步骤:
# 发送请求 -> 解析列表页 -> 解析详情页 -> 保存数据
# ==============================================================================

import requests
import csv
import os
from lxml import html

# ================= 1. 常量定义与目录准备 =================
# 首先定义好我们要抓取的URL和文件保存路径。
MOVIE_LIST_FILE = 'csv_data/movies_list.csv'
TMDB_BASE_URL = 'https://www.themoviedb.org'
TMDB_TOP_URL = 'https://www.themoviedb.org/movie/top-rated'

# 确保保存数据的目录存在,防止写入CSV时报错
os.makedirs('csv_data', exist_ok=True)


# ================= 4. 数据持久化:保存为CSV =================
# 使用Python内置的 csv.DictWriter,可以直接将字典列表写入CSV,非常适合这种结构化的爬虫数据。
# 💡 笔记重点:这里编码使用了 utf-8-sig 而不是 utf-8,这是为了防止用Excel打开CSV文件时出现中文乱码!
def save_all_movies(all_movies):
    """将电影数据列表保存为CSV文件"""
    with open(MOVIE_LIST_FILE, 'w', encoding='utf-8-sig', newline='') as f:
        # 定义表头
        fieldnames = ["电影名", "上映年份", "上映日期", "类型", "时长", "评分", "语言", "导演", "作者", "宣传语", "简介"]
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        
        writer.writeheader()      # 写入表头
        writer.writerows(all_movies) # 批量写入数据
    print(f"✅ 成功保存 {len(all_movies)} 条数据至 {MOVIE_LIST_FILE}")


# ================= 3. 详情页解析:XPath大显身手 =================
# 这是整个项目最核心、最难的部分。TMDB的详情页包含大量信息,我们需要用XPath精准定位。
#
# 重点XPath语法解析:
# - 获取电影名:利用Meta标签的OG协议获取。 //meta[@property="og:title"]/@content
# - 获取上映年份:利用 contains 模糊匹配class名。 //span[contains(@class, "release_date")]/text()
# - 获取导演和编剧:这里用了一个巧妙的列表推导式。先分别提取所有主创的名字和职位,
#   然后通过索引对应,筛选出职位包含 "Director" 或 "Writer" 的人。
#
# 数据清洗与容错处理:
# 网页数据往往是不规范的,或者某些电影缺少某项数据(比如没有宣传语)。
# 因此我们在提取后必须做判空和去空格处理。
def get_movie_info(movie_url, headers):
    """获取并解析单部电影的详情数据"""
    try:
        movie_response = requests.get(movie_url, headers=headers, timeout=60)
        movie_response.raise_for_status() # 检查请求是否成功
        print(f"🔍 正在抓取: {movie_url}")
        
        movie_doc = html.fromstring(movie_response.text)
        
        # 1. 基础信息提取
        names = movie_doc.xpath('//meta[@property="og:title"]/@content')
        years = movie_doc.xpath('//span[contains(@class, "release_date")]/text()')
        dates = movie_doc.xpath('//span[@class="release"]/text()')
        tags = movie_doc.xpath('//a[contains(@href, "/genre/")]/text()')
        cost_times = movie_doc.xpath('//span[@class="runtime"]/text()')
        scores = movie_doc.xpath('//div[contains(@class, "user_score")]/@data-percent')
        
        # 2. 语言提取 (注意:如果是中文界面是"默认语言",英文界面是"Original Language")
        lang_ps = movie_doc.xpath('//section[contains(@class, "facts")]//p[contains(., "默认语言") or contains(., "Original Language")]')
        
        # 3. 导演与作者提取 (列表推导式应用)
        crew_names = movie_doc.xpath('//li[contains(@class, "profile")]//a/text()')
        crew_roles = movie_doc.xpath('//li[contains(@class, "profile")]//p/text()')
        directors = [crew_names[i] for i, r in enumerate(crew_roles) if 'Director' in r]
        writers = [crew_names[i] for i, r in enumerate(crew_roles) if any(w in r for w in ['Screenplay', 'Novel', 'Writer'])]
        
        # 4. 宣传语与简介
        taglines = movie_doc.xpath('//*[contains(@class, "tagline")]/text()')
        overviews = movie_doc.xpath('//meta[@property="og:description"]/@content')

        # 5. 数据清洗与组装 (判空和去空格处理,例如:如果years列表有值,取第一个并去掉括号;否则返回空字符串)
        movie_info = {
            "电影名": names[0] if names else "",
            "上映年份": years[0].strip("()") if years else "",
            "上映日期": dates[0].strip() if dates else "",
            "类型": ",".join(tags) if tags else "",
            "时长": cost_times[0].strip() if cost_times else "",
            "评分": scores[0] if scores else "",
            "语言": lang_ps[0].xpath('string()').replace("默认语言", "").replace("Original Language", "").strip() if lang_ps else "",
            "导演": directors[0] if directors else "",
            "作者": writers[0] if writers else "",
            "宣传语": taglines[0].strip() if taglines else "",
            "简介": overviews[0] if overviews else ""
        }
        return movie_info
        
    except Exception as e:
        print(f"❌ 抓取失败 {movie_url}, 错误信息: {e}")
        return None


# ================= 2. 主函数:统筹全局 =================
# 主函数负责控制整体流程。首先请求排行榜页面,提取出所有电影的详情页链接,然后循环请求详情页。
def main():
    # 1. 添加请求头,伪装成浏览器,防止被基础反爬拦截
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
    }
    
    print("🚀 开始抓取TMDB高分电影排行榜...")
    response = requests.get(TMDB_TOP_URL, headers=headers, timeout=60)
    
    # 2. 解析列表页,获取电影详情链接
    document = html.fromstring(response.text)
    movie_list = document.xpath('//div[contains(@class, "poster-card")]')
    
    # 3. 遍历列表,抓取详情
    all_movies = []
    for movie in movie_list:
        # 提取相对路径并拼接成完整URL
        movie_urls = movie.xpath(".//a[contains(@href, '/movie/')]/@href")
        if movie_urls:
            movie_info_url = TMDB_BASE_URL + movie_urls[0]
            movie_info = get_movie_info(movie_info_url, headers)
            if movie_info:
                all_movies.append(movie_info)
                
    # 4. 保存数据
    if all_movies:
        print("数据抓取完毕,正在保存...")
        save_all_movies(all_movies)
    else:
        print("⚠️ 未抓取到任何数据,请检查网络或XPath规则。")

# 修复了原代码中 if name == 'main': 的拼写错误,正确的Python标准写法是 __name__ == '__main__'
if __name__ == '__main__':
    main()

💡 踩坑与进阶建议

在跟着老师敲完代码后,我自己总结了几点可以优化的方向,这也是从“初学者”向“进阶者”迈进的关键:

1. **修正语法错误**:原代码结尾写的是 `if name == 'main':`,这会导致主函数根本不会执行!正确的Python标准写法是 `if __name__ == '__main__':`(注意前后各有两个下划线)。
2. **反爬虫对抗**:TMDB有一定的反爬机制。如果不加 `User-Agent`,请求很容易被拒绝(返回403)。在优化版代码中我加上了请求头。如果抓取频率过高,建议加入 `time.sleep(2)` 进行延时,或者使用代理IP。
3. **异常处理(Try-Except)**:网络请求是不稳定的,如果某一部电影请求超时,原代码会导致整个程序崩溃。我在 `get_movie_info` 中加入了 `try-except`,保证一部电影失败不影响其他电影的抓取。
4. **语言本地化问题**:TMDB的网页语言会根据你的IP或Cookie变化。代码中提取语言时使用了 `contains(., "默认语言")`,如果你的网络环境默认是英文网页,这里需要改成 `Original Language`(优化版代码中已做兼容处理)。
5. **Excel乱码问题**:使用 `utf-8` 保存CSV时,用Excel打开中文会乱码。将编码改为 `utf-8-sig`(带BOM的UTF-8)就能完美解决。

## 🎯 总结
通过这个实战项目,我深刻理解了 请求-解析-存储 经典三步曲。特别是 `lxml` 的 `XPath` 语法,虽然一开始看那些 `//`、`contains` 觉得像天书,但掌握后发现它比正则表达式在解析HTML时好用太多了!

我们下一个项目见!👋

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐