豆瓣电影TOP250爬虫

豆瓣电影TOP250爬虫


这是一项Python爬虫课的期末作业。

前情提要

Python爬虫课的期末作业是自由选择一个网站爬取,那我就本着多一事不如少一事的原则选了豆瓣电影Top250这个最模板化的网站,而且网上也有很多很多例子可以借鉴。别的不说,我也挺喜欢看电影的,刚好理一遍,慢慢看这250个电影。不过期末周哪个孩子有时间看电影啊!

分析

打开豆瓣电影Top250,可以看到电影按十个一页降序排列在页面中,而且电影相应的海报图片和简介以及评分和一句话的评价都整合在一个框架当中。整个豆瓣电影TOP250排行榜共有250个电影,以25个一页分为10页。

点击第二页,可以看到网站链接的变化:https://movie.douban.com/top250?start=25,从其中可以知道start参数即此页及其之前页的电影数量,以此类推start=0为第一页,start=25为第二页,start=50为第三页……

通过肉眼观察和F12,可以知道每个电影所在的div框架是包含了他所有信息,所以要么通过正则来匹配需要爬的信息,要么通过bs4模块先提取需要的源码块再进行数据清洗。

代码

我为了逻辑更清晰,将要爬取的信息分为成了不同的模块去爬取,电影名称,电影评价,电影图片等等,但是缺点很明显,代码量很大,数据清洗要根据不同情况分批进行,再存到不同列表中

改进方法就是写一个爬取所有需要信息的正则与bs4模块提取的框架,将所有信息进行数据清洗,再放到一个列表中,这样效率会高很多,代码量也小。

此次代码用到了异常捕获,主要是为了防止图片下载失败,因为自己寝室里网络不错,运行完美,到了机房,访问豆瓣图片库有时候会G,所以要写一个重新尝试下载功能。

还有文件管理,启动程序前先检查需要的目录是否存在,是否有同名文件冲突,如果有则创建或删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# -*- codeing = utf-8 -*-
import re
import sqlite3
import time
import urllib.request
from bs4 import BeautifulSoup
from prettytable import PrettyTable

# 模拟浏览器
headers = ("User - Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36")
opener = urllib.request.build_opener()
opener.addheaders = [headers]

# 将opener安装为全局
urllib.request.install_opener(opener)

# 保存每页的链接
def save_img_url(data):
path="img_url.txt"
file=open(path,"a")
file.write(data+"\n")
file.close()

# 保存图片到本地
def save_img():
for p in range(0,10):
flag = True
url = "https://movie.douban.com/top250?start=" + str(p * 25)
data1 = urllib.request.urlopen(url).read().decode("utf-8")
# 将每页的链接保存到本地
save_img_url(url)
# 获取到所有海报图片链接头,无后辍.jpg
pat = re.compile(r'<img.*src="(.*?).jpg"') # .*?表示字符出现一次或0次,re.S表示忽略换行
img_url = pat.findall(data1)
for a_i in range(0, len(img_url)):
loop = True
# 使用一个while循环,在当爬取任意一张图片出现问题时,重新爬取一次
while loop:
try:
this_img = (img_url[a_i])
# 图片链接加入后缀.jpg
this_img_url = this_img + ".jpg"
print(this_img_url)
img_path = "img/" + str((a_i + 1) + p * 25) + ".jpg"
print("正在爬取第", str((a_i + 1) + p * 25), "张图片")
urllib.request.urlretrieve(this_img_url, img_path)
time.sleep(0.1) # 每张图片下载间隔0.1秒
loop = False # 如果没问题就不循环了,有问题这条不执行而执行except中的内容
except:
print("图片爬取错误,正在重试!")

# 保存所有电影数据
def save_all():
titles = []
kinds = []
ranks = []
quotes = []
viewns = []

for p in range(0,10):
# 设置url并解析html
url = "https://movie.douban.com/top250?start=" + str(p * 25)
data1 = urllib.request.urlopen(url).read().decode("utf-8")

#电影名称爬取
pat1 = re.compile(r'<span class="title">([^&]+)</span>',re.S)
# .*?表示字符出现一次或0次,re.S表示忽略换行 [^&]+即排除中间有&号的一次或多次 ()内即为爬到的东西
title = pat1.findall(data1)
# # 对得到的列表进行数据清洗
# for i in range(0,len(img_url)):
# img_url[i] = re.sub('&nbsp;/&nbsp;', '', img_url[i])
titles = titles + title
# print(titles)

# 电影种类爬取
pat2= re.compile(r'<br>(.*?)</p>',re.S) # .*?表示字符出现一次或0次,re.S表示忽略换行
kind = pat2.findall(data1)
# 对得到的列表进行数据清洗
for i in range(0,len(kind)):
kind[i] = re.sub('&nbsp;', '', kind[i])
kind[i] = re.sub(' ', '', kind[i])
kind[i] = re.sub(' ', '', kind[i])
kind[i] = re.sub('\n', '', kind[i])
kinds = kinds + kind
# print(kinds)

# 电影评分爬取
pat3 = re.compile(r'<span class="rating_num" property="v:average">(.*?)</span>',re.S) # .*?表示字符出现一次或0次,re.S表示忽略换行
rank = pat3.findall(data1)
# # 对得到的列表进行数据清洗
# for i in range(0,len(img_url)):
# img_url[i] = re.sub('&nbsp;/&nbsp;', '', img_url[i])
ranks = ranks + rank
# print(ranks)

# 电影评论人数爬取
url = "https://movie.douban.com/top250?start=" + str(p * 25)
data1 = urllib.request.urlopen(url).read().decode("utf-8")
# print(url)
pat4 = re.compile(r'<span>(.*?)</span>',re.S) # .*?表示字符出现一次或0次,re.S表示忽略换行
viewn = pat4.findall(data1)
# 对得到的列表进行数据清洗
del viewn[0: 6]
viewns = viewns + viewn
# print(viewns)

# 电影精华短评爬取
soup = BeautifulSoup(data1,"html.parser") # 指定Beautiful的解析器为“html.parser”
# 注:解析器负责把 HTML 解析成相关的对象,而 BeautifulSoup 负责操作数据(增删改查)。“html.parser” 是 Python 内置的解析器
for item in soup.find_all('div', class_="item"): # 查找符合要求的字符串,形成列表(注:class是一个类别/属性,需要加下划线,否则会报错)
# 这里的循环将每个item,即每个电影的代码框架遍历,共25个
pat5 = re.compile(r'<span class="inq">(.*)</span>')
# 从每个代码框架中找出评价,若没有评价,则将评论留空
item = str(item)
quote = "".join(re.findall(pat5, item)) # 将列表元素转换为字符串
if len(quote) != 0:
quotes.append(quote) # 添加
else:
quotes.append(" ") # 留空
# print(quotes)
print("第", p+1, "页爬取完毕")
print("---------------------------")
print("检查数据正确性:")
print("影片名称个数", len(titles))
print("影片种类个数", len(kinds))
print("影片评分个数", len(ranks))
print("影片评分人数个数", len(viewns))
print("影片精华简评个数", len(quotes))

#将数据保存到SQL数据库
conn = sqlite3.connect('Spider.db')
print("建立并打开数据库 Spider.db 成功!")
c = conn.cursor()
c.execute('''
create table Movie(ID int primary key not null, title varchar(50), kind varchar(50), ranks varchar(50), viewer varchar(50), quote varchar(50));
''')
print("表Movie创建成功!")

print("开始向Movie表插入数据")
for i in range(0,len(titles)):
c.execute('''
insert into Movie values(?,?,?,?,?,?);
''', (i+1, titles[i],kinds[i],ranks[i],viewns[i],quotes[i]))
print("数据插入Movie成功!")
conn.commit()
conn.close()

def select_sql():
# 创建放置查询结果的输出表格,设置其表头
tb = PrettyTable()
tb.field_names = ["ID", "电影名称", "种类", "评分", "评论人数", "优秀短评"]

conn = sqlite3.connect('Spider.db')
c = conn.cursor()
print("开始查询SQL数据库的Movie表:")
cursor = c.execute("SELECT * FROM Movie")
for row in cursor:
tb.add_row([row[0], row[1], row[2], row[3], row[4], row[5]])
print(tb)
print("SQL数据库查询成功!")
conn.commit()
conn.close()

def main():
print("开始爬取数据!")
save_all()
print("数据爬取完毕!4秒后开始爬取图片")
print("---------------------------")
time.sleep(4)
save_img()
print("图片爬取完毕!请在img目录下查看对应ID图片")
print("4秒后开始查询数据库Movie表")
print("---------------------------")
time.sleep(4)
select_sql()

if __name__ == "__main__":
main()

小结

这些代码陆陆续续写了十几个小时,因为之前上课一直摸鱼,老师讲的也很含糊,并没有想让我会的意思,于是基本上很多爬虫知识都是临时搜索和复制黏贴来的,不过有认真思考程序的运行思路与错误优化。


评论