栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Python 爬虫实战 - 豆瓣 TOP250

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python 爬虫实战 - 豆瓣 TOP250

项目概述

  • 爬取目标:爬取TOP250上每一部的电影详情页,清洗数据,保存为 CSV 文件;
  • 涉及知识:requests、lxml、Xpath、re、csv、list 操作(Python 爬虫基础 里都有)
  • 完整代码:GitHub - Shawshank-LIUYU/Python3-Crawler-projects

文章目录

# 1. 获取详情页连接模块

# 2. 解析详情页模块

## 2.1 获取响应内容

## 2.2 Xpath 解析 [排名, 电影名, 评分, 评价人数]

## 2.3 re 解析 [制片地区, 语言] 

## 2.4 Xpath 解析 [主演]

### 2.4.1 /text() 与 //text()

 ### 2.4.2 Xpath 的 string(.) 用法

### 2.4.3 例外:纪录片

## 2.5 返回解析数据

# 3. 数据储存模块

## 3.1 整理成列表格式

## 3.2 列表存入 csv

# 4. 完整代码

# 5. 数据截图

# 6. 需要改进之处


# 1. 获取详情页连接模块

""" 获取榜单页单页的详情页地址
     用列表的形式返回 """

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

def index_page(film_rank_number):
    index_url = 'https://movie.douban.com/top250?start={}&filter='.format(film_rank_number)
    index_res = requests.get(url=index_url, headers=headers)
    encoding = cchardet.detect(index_res.content)['encoding']
    index_html = index_res.content.decode(encoding)
    index_tree = etree.HTML(index_html)
    details_urls = index_tree.xpath("//li/div/div/a/@href")
    return details_urls

# 2. 解析详情页模块

""" 获取单个电影详情页html结构,用 Xpath 解析html / 正则表达式(re)解析,拼接成列表,
     返回一个列表,参照csv表格一行的格式 """


## 2.1 获取响应内容

先对 #1模块 获取到的详情页 URL 发送请求,获取响应内容,然后再使用 Xpath 提取相关信息,
# 这里的 cchardet模块 是可以自动检测网页的 document.charset 的,可以做到全自动解析 html

movie_page_res = requests.get(url=url, headers=headers)
movie_page_encoding = cchardet.detect(movie_page_res.content)['encoding']
movie_page_html = movie_page_res.content.decode(movie_page_encoding)
movie_page_tree = etree.HTML(movie_page_html)

## 2.2 Xpath 解析 [排名, 电影名, 评分, 评价人数]

对于这几个参数,简单的 Xpath 即可。无非是通过节点、属性或者text()进行筛选。

# 排名
ranking = movie_page_tree.xpath('//span[@]/text()')
# 电影名
name = movie_page_tree.xpath('//h1/span[1]/text()')
# 评分
score = movie_page_tree.xpath('//strong/text()')
# 评价人数
review_number = movie_page_tree.xpath('//a/span[@property="v:votes"]/text()')

## 2.3 re 解析 [制片地区, 语言] 

这两个参数,在页面结构里的位置比较尴尬,没有属性可以检索,所包含的节点层次关系也有点复杂,因此选择 正则表达式(re) 暴力匹配

# 制片国家/地区
nation = re.findall(r'制片国家/地区:(.*?)
', movie_page_html) # 语言 language = re.findall(r'语言:(.*?)
', movie_page_html)

如图,"美国" 后是
,但是在正则表达式中填入 
返回值是 [],但换成
则正确.

事实上,
 是 "美国" 的结尾,在 HTML 中,
标签没有结束标签,而在 XHTML 中,
标签必须被正确地关闭:
,有的时候还有
。显示出的是
,但为了这些模式都兼容,选择用正则表达式的 ? ,即匹配 (.*?)
,具体内容见 re库笔记 ,有思维导图方便记忆.


## 2.4 Xpath 解析 [主演]

这是相对最复杂的一个解析,涉及到 html 结构的子孙节点,Xpath 的 string(.) 用法,以及特殊情况的排除.


### 2.4.1 /text() 与 //text()

/ 是子节点,//是子孙节点,子节点的文本没有东西,有点东西的是孙子,结果如图:

输出一下是这样:

因此处理需要先 del performer_list[0] 两次,再 "".join(performer_list),最后的结果就是 「张国荣/张丰毅/…」 ,即我们所需的效果,代码如下:

performer_list = movie_page_tree.xpath("//div[@id='info']/span[3]//text()")
del performer_list[0]
del performer_list[0]
performer = ["".join(performer_list)]

 ### 2.4.2 Xpath 的 string(.) 用法

先看 span[3] 的 RESULTS,这是整个节点包含的内容,但是程序中 Xpath 的返回类型是个 [element],输出是 [] 这样子的东西(我乱打的^^),而 text() 方法得全搞下来自己处理数据。事实上,当我们需要拿取嵌套节点的内容的时候,使用 string(.) 方法效果更好.

不过这时候,爬出来的结果有 "主演: " 字样,需要给他搞掉,这里想了两种方法:第一种是 actor_list.lstrip(),一个是用简单的正则表达式 re.sub(r'主演: ', "",actor_list) ,我用的是前者:

value = parse_movie.xpath("//div[@id='info']/span[3]")
performer = value[0].xpath('string(.)')  # [0]指list的第一个元素是element
performer = [performer.lstrip('主演:')]

### 2.4.3 例外:纪录片

 问题在于,有的纪录片是没有主演的,如我很喜欢的郭柯导演的电影《二十二》,他就没有主演这个选项,用 span[3] 返回的是 "类型: 纪录片",因此为之单设一种情况,即纪录片,选择返回string "类型: 纪录片" 即可.

performer_list = movie_page_tree.xpath("//div[@id='info']/span[3]//text()")
if len(performer_list) >= 3:
    del performer_list[0]
    del performer_list[0]
    performer = ["".join(performer_list)]       # 纪录片是例外
else:
    performer = performer_list

## 2.5 返回解析数据

列表之间的连接,只要 + 就可以.

return ranking + name + score + review_number + types + nation + language + time_span + director + scripter + performer + imdb_url

# 3. 数据储存模块

## 3.1 整理成列表格式

设置表头,每条电影的信息 append 入表格即可,最后调用列表存入函数,见 ## 3.2

csv_headers = ['排名','电影名','评分','评价人数','电影类型','国家','语言','时长','导演','编剧','主演','IMDb链接']
csv_rows = [csv_headers]

try:
    for i in range(0, 10):  # 应该是10,这里用作测试,250需加随机时间
        movie_urls = index_page(i * 25)
        for movie_url in movie_urls:
            res = parse_movie_page(movie_url)  # return list
            csv_rows.append(res)
            print('第 {} 条电影信息存入内存!'.format(len(csv_rows) - 1))
            time.sleep(random.random() * 3)
except:
    traceback.print_exc()

## 3.2 列表存入 csv

思路是用列表进行存储,即 cursor.writerows(rows) 函数,因此只需要把数据整合成列表的列表即可,即一条电影的信息为一个列表,250个列表组成一个新的列表即可.

def save_rows_to_csv(rows):
    with open('douban.csv', 'a', encoding="utf-8-sig", newline='') as file:
        f_csv = csv.writer(file)
        f_csv.writerows(rows)

# 读写方式选择 a 模式(添加),这样不用总是删除,也可以对比之前的错误情况是否有改进,encoding 选择 "utf-8-sig" ,可以规避乱码(在对csv文件写入有日韩文字的时候,需要用 "utf-8-sig",对txt文件写入中日韩文字的时候,"utf-8-sig" 和 "utf-8" 均可,具体参照 utf-8-sig 编码格式)

# newline = '' 可以解决输入csv表有空行的情况,如图

其实还有 字典写入csv 的方法,即游标为 DictWriter(file,headers),在解析详情页模块先设立个headers,然后再列表转dict —— return(dict(zip(headers,list))),最后写入就先 writeheader() ,再writerows(rows).


# 4. 完整代码
# -*- coding = utf-8 -*-
# @Time : 2021/10/14 10:28
# @Author : LIUYU
# @File : douban250_crawler.py

import random
import time
import requests
from lxml import etree
import cchardet
import re
import csv
import traceback

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

def index_page(film_rank_number):
    index_url = 'https://movie.douban.com/top250?start={}&filter='.format(film_rank_number)
    index_res = requests.get(url=index_url, headers=headers)
    encoding = cchardet.detect(index_res.content)['encoding']
    index_html = index_res.content.decode(encoding)
    index_tree = etree.HTML(index_html)
    details_urls = index_tree.xpath("//li/div/div/a/@href")
    return details_urls

def parse_movie_page(url):
    """
        xpath, [].append()
        return []
    """
    # 1. get tree
    movie_page_res = requests.get(url=url, headers=headers)
    movie_page_encoding = cchardet.detect(movie_page_res.content)['encoding']
    movie_page_html = movie_page_res.content.decode(movie_page_encoding)
    movie_page_tree = etree.HTML(movie_page_html)

    # 2. Xpath
    # 排名
    ranking = movie_page_tree.xpath('//span[@]/text()')
    # 电影名
    name = movie_page_tree.xpath('//h1/span[1]/text()')
    # 评分
    score = movie_page_tree.xpath('//strong/text()')
    # 评价人数
    review_number = movie_page_tree.xpath('//a/span[@property="v:votes"]/text()')
    # 类型
    list_type = movie_page_tree.xpath('//span[@property="v:genre"]/text()')
    types = ["/".join(list_type)]

    # 制片国家/地区
    nation = re.findall(r'制片国家/地区:(.*?)
', movie_page_html) # 语言 language = re.findall(r'语言:(.*?)
', movie_page_html) # 时长 time_span = movie_page_tree.xpath('//span[@property="v:runtime"]/text()') # 导演 director_list = movie_page_tree.xpath("//div[@id='info']/span[1]/span[@class='attrs']/a/text()") director = ["".join(director_list)] # 编剧 scripter_list = movie_page_tree.xpath("//div[@id='info']/span[2]/span[@class='attrs']/a/text()") scripter = ["".join(scripter_list)] # 主演 performer_list = movie_page_tree.xpath("//div[@id='info']/span[3]//text()") # 主演; /text()是子节点, //text()是子孙节点,可用 if len(performer_list) >= 3: del performer_list[0] del performer_list[0] performer = ["".join(performer_list)] # 纪录片是例外 else: performer = performer_list # IMDb imdb_url = re.findall('IMDb:(.*?)
', movie_page_html) # return list return ranking + name + score + review_number + types + nation + language + time_span + director + scripter + performer + imdb_url def save_rows_to_csv(rows): with open('douban.csv', 'a', encoding="utf-8-sig", newline='') as file: f_csv = csv.writer(file) f_csv.writerows(rows) if __name__ == '__main__': csv_headers = ['排名','电影名','评分','评价人数','电影类型','国家','语言','时长','导演','编剧','主演','IMDb链接'] csv_rows = [csv_headers] try: for i in range(0,10): movie_urls = index_page(i*25) for movie_url in movie_urls: res = parse_movie_page(movie_url) # return list csv_rows.append(res) print('第 {} 条电影信息存入内存!'.format(len(csv_rows)-1)) time.sleep(random.random()*3) except: traceback.print_exc() save_rows_to_csv(csv_rows)

# 5. 数据截图


# 6. 需要改进之处

豆瓣电影有反爬机制,这次我是用 time.sleep(random.random()*3),即随机时间暂停的方法,无疑是很花费时间的,总共用的时间差不多是 0.5*3*250 = 375s —— 一共访问了260个网页,意识到了爬虫的局限性。之后可以考虑使用 多个代理IP、多个 User-Agent 去爬取网页.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/329924.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号