豆瓣是个好网站,能学习各种知识、能社交、能找房子、找驴友等。
于我而言,最大的快乐在于豆瓣能提供丰富的在线数据,如:书/电影/音乐等,不仅有这些的基础数据,还有各种榜单/评论等,数据结构也相当丰富。在本人最开始写前端的时候,曾在网上寻找各种在线数据来进行页面的渲染,直到后面找到豆瓣的时候发现竟然还有官方的API提供,可畏业界良心。后面学习各种框架都是界面参考豆瓣,数据直接用API获取,方便得不行。后面学习小程序的时候,也想用直接用豆瓣的API,发现小程序直接调用的时候会出现403(好像跟小程序的UA有关系),后面自己本地起了nginx做了代理就ok了。最近想学点新东西,突然想先去看看豆瓣的API文档,结果发现网站打不开了,所有的api都关掉了。那正好,就学Python吧,我记得我第一次听说Python好像爬虫很厉害,那就试试吧。
之前有看过一些基本的语法,贴上几个网站 官方中文文档, runoob.com-超全的学习网站
- 环境准备(Mac) 其它环境安装教程
Mac和Linux最新版好像都带自带了python,我自己是Mac,所以用试了一下:
python
可以看出默认的python是2.7.16,目前最新版本是3.x。
退出,狂按Command + C,发现没反应,再输入exit,结果提示
>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit
原来python命令的退出方式跟其它的脚本不一样呀,Mac上为 command+d 或者 输入exit() 加回车。
之前看的文档上有提过,可以使用python3来使用最新版本的python,试了一下:
Mac也内置了python3.7.3,目前官方最新版本为3.8.2,还是比较新的,可能由于我的Mac OS是最新版的原因吧。
python跟node/java一样,安装完环境之后可以直接在命令行里面运行一些简单的指令,如下:
说明:
print()是打印语句,类似于console()等。
其中有一句print("2+3=", 2+3), 在print()方法中可以进行简单的运算。
in = input(); print("输入的是:"+ in);
in = input(); print("输入的是:"+ in);
^
SyntaxError: invalid syntax // 这里出错,是因为in是一个关键字,不能用来定义变量
python的其它语法就需要在上面我贴的两个网址去学习哈。
上面演示的是在命令行运行简单代码,Python也支持以文件的方式运行。
后面我用vs code来进行开发。
- 爬虫实战
新建一个test.py文件,里面可以写上
print('my first py program ')
然后,在vs 的termial里面运行 test.py,记得在运行之后要保存文件哦。
python3 test.py
my first py program
ok,这样我们就知道怎么用一个文件去执行我们写的python代码了。
接下来,我们先分析一下豆瓣电影(top250)爬虫的需求:
- 最终的数据结构:json格式,方便传输与转换。
单一电影的数据结构,最后生成一个list
{'ranking': '25', 'title': '触不可及', 'orther_title': '/闪亮人生(港) / 逆转人生(台)', 'director': ' 奥利维·那卡什 Olivier Nakache / 艾力克·托兰达 Eric Toledano', 'year': '2011', 'region': '剧情 喜剧', 'douban_href': 'https://movie.douban.com/subject/6786002/', 'average_rating': '9.2', 'votes': '684449', 'short_quote': '满满温情的高雅喜剧。'}
-
数据源网址:豆瓣电影 Top 250
爬虫的大致原理是:用http方法获取到源网址的整体dom结构,然后根据正则表达式来进行匹配,最终过滤出想要的数据。
所以我们先去源网站观察一下dom结构,
我们需要的数据,是包含在一个<ol></ol>标签对里面,而且经过搜索可以发现,整个dom树里面只含有一个<ol></ol>标签对。
我们知道了dom的结构,就可以将我们所需要的数据一一拆解出来了。
这样我们的需求就清楚了,接下来就要看怎么具体实现了。 先让我们的程序可以访问到源网址,https://movie.douban.com/top250
# test.py
# print('my first py program')
# 1、引入需要要的包
# 网络请求相关的包
from urllib.request import urlopen, Request
import ssl
# 正则相关的包
import re
# 配置https请求
ssl._create_default_https_context = ssl._create_unverified_context
# 设置数据源地址
source_url = 'https://movie.douban.com/top250'
# 自定义http request请求头,豆瓣默认只允许部分UA进行访问 这里自定义User-Agent成浏览器的UA
custom_headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
# 初始化Request
movie_request = Request(source_url, headers = custom_headers)
# 发出request请求
movie_response = urlopen(movie_request)
# 将读取结果进行utf-8解码,避免乱码
html = movie_response.read().decode('utf-8')
# 测试打印结果
print(html)
我们在termial里面运行代码,可以看到正常的打印结果,如下图:
这是整个页面的dom结果,这个效果和我们在浏览器里面通过右键>显示网页源代码所看到的内容是一致的。
- 接下来,我们将所需要的<ol></ol>标签对里面的内容从完整的dom树里面匹配出来,并保存至一个本地文件里面。
我们先看一下单个电影数据在dom中的结构是什么样的。
<li>
<div class="item">
<div class="pic">
<em class="">8</em>
<a href="https://movie.douban.com/subject/1295124/">
<img width="100" alt="辛德勒的名单" src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p492406163.jpg" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1295124/" class="">
<span class="title">辛德勒的名单</span>
<span class="title"> / Schindler's List</span>
<span class="other"> / 舒特拉的名单(港) / 辛德勒名单</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 史蒂文·斯皮尔伯格 Steven Spielberg 主演: 连姆·尼森 Liam Neeson...<br>
1993 / 美国 / 剧情 历史 战争
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.5</span>
<span property="v:best" content="10.0"></span>
<span>753795人评价</span>
</div>
<p class="quote">
<span class="inq">拯救一个人,就是拯救整个世界。</span>
</p>
</div>
</div>
</div>
</li>
单个电影是包含在<li></li>标签对里面的,而<ol></ol>里面含有多个<li></li>标签对,所以首先要把这些<li></li>标签对一个个的匹配出来,然后写到本地文件中。
具体代码如下:
# 定义一个函数,获取所有跟电影相关的<li></li>
def getMovieItems(content):
# 匹配正则表达式 [\s\n]*表示,在<li>标签和<div之前可能会有多个空格(\s)或者换行符(回车或\n) .*表示 除换行符以外的字符可能多次重复 ?表示匹配0个或1个由前面的正则表达式定义的片段
reg = '<li>[\s\n]*<div.*?</li>'
# 生成一个正则表达式对象 re.S表示 使 .匹配包括换行在内的所有字符
item_wrap = re.compile(reg, re.S)
print(item_wrap)
# 在字符串中找到正则表达式所匹配的所有子串,并返回一个列表,如果没有找到匹配的,则返回空列表
movie_items = re.findall(item_wrap, html)
return movie_items
# 将从源网址获取到的结果进行匹配
movies = getMovieItems(html)
# 打印匹配结果 len()表示数组长度
print(movies, len(movies))
运行程序, python3 test.py,可以看到下图的结果:
我们打印了匹配后的数组,以及数组长度25。 由于250条数据量太大,默认一页只显示25条,我们暂且先看这25条的数据,后面可以增加分页参数,获取剩下的数据。
ok,到这里我们就已经获取到我们所需要的电影数据源,我们要在此基础上进行数据匹配:
# 定义一个列表,用来存最终数据
list = []
# 循环遍历
for m in movies:
# 删除掉
m = re.sub(' ', '', m)
dic = {}
# 评分
reg = '<em class="">(.*)</em>'
ranking = re.findall(re.compile(reg), m)
print(ranking)
dic['ranking'] = ranking[0]
# title
reg = '<span class="title">(.*)</span>'
title = re.findall(re.compile(reg), m)
print(title)
dic['title'] = title[0]
# orther_title
reg = '<span class="other">(.*)</span>'
orther_title = re.findall(re.compile(reg), m)
print(orther_title)
dic['orther_title'] = orther_title[0]
# type_people 导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br> 1994 / 美国 / 犯罪 剧情
reg = r'<p class="">(.*?)</p>'
type_people = re.findall(re.compile(reg, re.S), m)[0]
# 删除掉
type_people = re.sub(' ', '', type_people)
# 删除掉 <br>;
type_people = re.sub('<br>', '', type_people)
# 删除掉 回车/换行符;
type_people = re.sub('\n', '', type_people)
# 删除掉 空格;
type_people = re.sub('\s', '', type_people)
print('type_people', type_people)
# director 导演 匹配导演: 与 主演之前的内容 有些可能没有主演的演字...
reg = r'导演:(.*)主'
director = re.findall(re.compile(reg, re.S), type_people)
print(director)
dic['director'] = director[0]
# year 年份 匹配4位数字
reg = r'\d+'
year = re.findall(re.compile(reg), type_people)
print(year)
dic['year'] = year[0]
# actors 主演 从主演: 开始 截取至 4位年份止
reg = r'主演:(.*)\d{4}'
director = re.findall(re.compile(reg), type_people)
print(director)
if (len(director)):
dic['director'] = director[0]
# region 国家 从4位年份+'/'截取至下一个'/'
reg = r'\d{4}/(.*)/'
region = re.findall(re.compile(reg), type_people)
print(region)
if (len(region)):
dic['region'] = region[0]
# type 类型
reg = r'\d{4}/.*/(.*)'
region = re.findall(re.compile(reg), type_people)
print(region)
if (len(region)):
dic['region'] = region[0]
# douban_href <a href="https://movie.douban.com/subject/1292052/" class="">
reg = r'<a href="(.*?)">'
douban_href = re.findall(re.compile(reg, re.S), m)
print(douban_href)
dic['douban_href'] = douban_href[0]
# average_rating
reg = '<span class="rating_num" .*>(.*?)</span>'
average_rating = re.findall(re.compile(reg), m)
print(average_rating)
dic['average_rating'] = average_rating[0]
# votes <span>1944072人评价</span>
reg = '<span>(.*)人评价</span>'
votes = re.findall(re.compile(reg), m)
print(votes)
dic['votes'] = votes[0]
# short_quote <span class="inq">
reg = '<span class="inq">(.*)</span>'
short_quote = re.findall(re.compile(reg, re.S), m)
print(short_quote)
dic['short_quote'] = short_quote[0]
list.append(dic)
print(list)
我们执行python3 test.py,就可以看到结果:
这样,我们就已经获取到了完整的25条电影数据了,接下来,我们可以将这些数据写入到本地文件,可以拿来给别人分享或者在其它程序里面用啦,具体代码如下:
import json
json_str = json.dumps(list, indent=4, ensure_ascii=False)
json_file = open('movie-douban-top250.json', 'w+')
json_file.writelines(json_str)
可以直接写在代码最后面,运行之后在test.py同级目录会生成一个movie-douban-top250.json的文件,如下图所示:
好了,到此我们已经完整的实现了之前的需求,获取原网址的电影数据,并生成了一个本地的json文件。
写代码的过程中查询了很多文章(感谢万能的bing...),花了好长时间才写完,写文章的时候又花了好几个小时,真心不容易。
最后,贴上完整的代码,大家可以下载试试哦。
https://github.com/realfly08/learn-python/blob/master/test.py
觉得可以的话,请点个赞哦!