今天的文比较长:加代码一共8296字,不算代码一共:3746.
阅读时间较长,内容仅做参考,
之前看了不少大厂对实习生的招聘要求,对python实习生的要求中都要求要有爬虫编写的经验,这两周的爬虫项目让我学到了很多,所以不论是为了入门,还是为了提高,写写小型的项目总是很有用的。
希望用到的技能:
- 爬虫基础知识
- BeautifulSoup的使用
- 多进程
- 操作数据库
- 使用队列
- 文件操作
- MATLAB画图统计,实现数据可视化
- 下载图片
- 正则表达式
- 使用代理池、防止封锁
- 克服反爬机制等
在写爬虫之前我是希望用到这些知识的,有些我了解,有些没接触过,加粗的是这次用到的,下面来一步一步开始我们的爬虫,我会随时介绍我在编写的时候遇到的问题。
先来看下成果:
image.png
image.png
image.png
image.png
还有数据库,我用的数据可视化工具Navicat:
image.pngimage.png
很多爬虫教程都是在爬妹子图、美女图、啥啥图,这些对身体不好。不过我也帮室友爬过他们感兴趣的网站,就是那个解数学题的那个。我们正经一点!
首先豆瓣网站对与爬虫工程还是十分友好的,在写爬虫之前,我们先来看一下这个网站的robots.txt文件:https://movie.douban.com/robots.txt
User-agent: *
Disallow: /subject_search
Disallow: /amazon_search
Disallow: /search
Disallow: /group/search
Disallow: /event/search
Disallow: /celebrities/search
Disallow: /location/drama/search
Disallow: /forum/
Disallow: /new_subject
Disallow: /service/iframe
Disallow: /j/
Disallow: /link2/
Disallow: /recommend/
Disallow: /doubanapp/card
Sitemap: https://www.douban.com/sitemap_index.xml
Sitemap: https://www.douban.com/sitemap_updated_index.xml
# Crawl-delay: 5
User-agent: Wandoujia Spider
Disallow: /
从这份文件中,我们可以看到:User-agent: *,这是指的所有的爬虫,它下面的Disallow后面的所有的文件夹都不允许爬。# Crawl-delay: 5这是说的是爬虫的延时,不能太快,不然会增加豆瓣服务器的负担。我们为了学习当然是不会故意为难人家的服务器。
User-agent: Wandoujia Spider
Disallow: /
这两句就有意思了,豌豆荚的爬虫是不允许爬所有的文件的。
当然上面说的所有的规定都只是规定,你如果非要爬,那也不是做不到,但是没有必要。我们没必要跟别人作对,如果你真的爬到了什么不该知道的东西,那你可能就凉了。爬虫礼仪还是很重要的!
所以我们先去看了我们需要的内容在哪个文件夹下:
250电影列表:https://movie.douban.com/top250
——/top250/
电影详细信息:https://movie.douban.com/subject/1292052/
——/subject/
电影海报:https://movie.douban.com/photos/photo/480747492/
——/photos/
这三个目录在上面的robotsx.txt文件是没有禁止的,所以可以爬取,但是delay是要求5秒。
。。。。。。这有点长了,那理想情况:5250=1250s,在加上30张海报,530*250=37500s,这得猴年马月??所以我都设置为了1s,而且中间加上了一些BeautifulSoup的处理时间,差不多了。
robots.txt看完了,接下来就是应该分析我们需要哪些步骤来完成整个项目,说实话,这个项目不算大,但是也不小,算是目前我写过的最大的爬虫了。。。。
看一下首页:
top250首页
页面地址非常简单。这是第一页,每一部电影的排名,电影名称等信息都是以一个长方形的item展示在页面上
在首页的下面可以清楚的看到每一页25部电影,一共10页,一共250个items。非常规范,这对我们编写爬虫是非常有利的。
第一页
每部电影的详情页的地址非常清晰,页面上的信息也非常丰富,基本上这一页能看到的东西,我都想拿下来。
详情页
再来看一下海报的地址,浏览器直接点击电影封面就行:
海报
可以发现一部的电影的照片还是不少的,No.1就有535张,全部爬下来都行,但是没有必要,所以我们就取30张海报就行。
好了,我们在浏览器里用眼睛看到了我们想要的东西,电影信息,电影海报,这是我们需要的。
网站的结构是这样的
排行榜——10页
详情页面——一页
海报页面——有多页(只取30张)
所以根据网页结构,以及我们需要的数据内容,可以将获取信息的步骤分为:
步骤:
- 爬取10页排行榜地址
- 爬取所有250部电影详情页地址
- 处理每一部电影
3.1 爬取详情页
3.1 清洗数据
3.2 存储数据
3.3 爬取电影海报页面的30张海报的地址
3.4 下载海报
函数式编程:
大概的步骤是这样的,因此我们就可以先写出main函数:
def main():
#1. 爬取10页排行榜地址
ten_url=get_ten_page_url()
#2. 爬取所有250部电影详情页地址
movie250infos_url=get_250movie_info_url(ten_url)
#3. 处理每一部电影
for i in movie250infos_url:
#3.1 爬取详情页
info=get_info(i)
#3.1 清洗数据
info=configue_info(info)
#3.2 存储数据
save_info(info)
#3.3 爬取电影海报页面的30张海报的地址
img=get_img(info)
#3.4 下载海报
save_img(img)
接下来只要完成每一个函数的功能即可:
1. 爬取10页排行榜地址
有10页,所以现在来看一下下一页的地址是怎样的:
可以看出,
https://movie.douban.com/top250?start=25&filter=
这地址还是非常清楚的,start=25,filter=。这意思就是是从第25号开始,过滤器值为空。
这里就有问题了,那第一页的地址和第二页的地址结构是不一样的,而且明明第一页有25部电影,他为什么是start=25??,这对学编程的人来说,显而易见,我们数数是从0开始的,第一部0,第二部是1,第三部是2....,以此类推,所以我带着好奇心试了这个地址:https://movie.douban.com/top250?start=0&filter= image.png
哈哈。跟之前的/top250/显示的是一样的。那么filter=是什么意思呢?
可以看到第一部电影的item的右上角,有一个我没看过的选项,这应该就是需要过滤的东西,但是这里对我们没有影响,所以我们可以很容易的得到全部10页排行榜的地址:
#获得10页包含250个简介的页面地址
def get_ten_pageurl():
#array=["https://movie.douban.com/top250?start=0&filter="]
array=[]
for i in range(0,250,25):
array.append("https://movie.douban.com/top250?start="+str(i)+"&filter=")
return array
这里可以发现,#array=["https://movie.douban.com/top250?start=0&filter="]
这一句是我用来测试代码用的。因为,一共10页,你在后面试运行时,不可能每次都从头开始,这样会浪费很多时间。你可以像我一样,将一些需要循环的处理的列表设置成只有一个值,这样就不会等到先处理完10页链接,在执行你需要的地方。这是一个调试的好方法,但是在最后的时候,你要把他注释掉,别忘记了。
这里我们是非常幸运的,并没有遇到用ajax来换页的情况,在之前我爬取拉勾网时,就遇到了,下一页的地址和上一页的地址是一样的,他是通过ajax动态交互技术实现的,所以获取下一页的地址就成了问题。当然解决它也是很容易的,等下次写爬虫我在详细介绍ajax页面的爬取。
2. 爬取所有250部电影详情页地址
前面的10页的地址的获取不算是爬虫,只能算是一个简单的for循环。下面我们来看一看如何爬取250部电影的详情页地址:
先来看一看网页的代码,分析下结构,打开我们要爬取的页面按F12,或者直接在items上右键->检查元素:
image.png
就会出现下面的情况:
image.png
可以看到直接找到了我们需要的地址。
当然,如果你按的F12,出来的不是这样的,你需要点击元素
然后点击这个 image.png
然后将鼠标移到电影信息上,单击。就会出来一样的东西。
然后我们来看一下我们需要的详情页的地址在哪个位置:
image.png
可以看出,整个页面是用
<ol>
<li><\li>
<li><\li>
<li><\li>
<li><\li>
<li><\li>
<\ol>
这样的结构,每一个<li>标签里包含一个div标签,里面又有pic和info两个标签
image.png
展开里面的全部标签,可以看出这里包含了我们需要的大部分信息,但是还不够,我们需要像详情页里那样详细的信息,不过,这里的信息我们页可以保存一部分。这里我只拿了排名、电影名、电影详情页地址,这三个信息。观察html文件可以发现,这三个信息都在class="pic"的a标签里,所以只要取这一小段就行,然后获取信息就行。利用BeautifulSoup将页面上的25部电影的信息筛选出来,然后for循环处理,提取信息保存。
# 得到每一个电影的详情页地址
def get_250movie_page_url(ten_pageurl,Directory):
"""
输入10页地址
将top250 的电影首页地址保存下来,同时存到队列中和本地text
"""
if not os.path.exists(Directory):
os.makedirs(Directory)
url_queue=MyQueue()
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
conn.commit()
for page_url in ten_pageurl:
try:
html = urlopen(page_url)
bsobj = BeautifulSoup(html, features="html.parser")
# 得到当前页面上25个包含序号、详情页的地址的div标签,存为列表
movie_info_items = bsobj.findAll("div", {"class": "pic"})
for movie_info in movie_info_items:
try:
movie_id = int(movie_info.find("em").get_text())
movie_info_url = movie_info.find("a").attrs["href"]
movie_name=movie_info.find("img").attrs["alt"]
url_queue.push(movie_info_url)
with open(Directory + "/250homepage_url.txt", "a") as f:
f.write(str(movie_id))
f.write("\t")
f.write(movie_name)
f.write("\t")
f.write(movie_info_url)
f.write("\n")
print("获取movie%s详情页url成功"%(movie_id))
cursor.execute('insert into movie_250url '
'(id, name,url) '
'values (%s, %s,%s)',
[movie_id, movie_name, movie_info_url])
conn.commit()
except:
print("获取movie详情页url失败!")
#continue
time.sleep(2)
except:
print("页面%s处理失败"%(page_url))
time.sleep(1)
continue
cursor.close()
return url_queue
可以发现这个函数很长,因为这里包含了很多操作。这里我将250个信息存在了队列里,因为,我不打算使用多线程了。存在队列里,队列先进先出,没毛病。然后用了os新建文件夹。用了数据库,将排名、电影名、地址存为一张数据表。用了txt文件读写,将这些信息保存在了txt文本中。用了try。。。except。。。语句错误调试,以防突发问题,从而不影响整个程序。用了time模块,爬虫间隔2s。最后返回一个队列。
可以发现我用的是urllib的urlopen,而不是requests的get。这个问题以及BeautifulSoup的使用我以后会单独拿出来写。
3. 处理每一部电影
得到了250个页面的地址,都存在队列里。接下来处理他们。
3.1 爬取详情页
我直接将队列的头元素传入函数,获取页面,并新建为一个BeautifulSoup对象返回
#获取详情页
def get_movie_info(url):
try:
html = urlopen(url)
bsobj = BeautifulSoup(html, features="html.parser")
print("获取详情页面成功",url[-8:])
time.sleep(1)
return bsobj
except:
print("详情页面%s获取失败"%(url[-8:]))
time.sleep(1)
return None
bsobj = BeautifulSoup(html, features="html.parser")
这我现在还不太清楚,这是运行时加的——features="html.parser",没细看。但是不加会给个警告warning。
3.1 清洗数据
得到了bs对象,接下来就是处理他了。
详情页的结构比前面的250电影的结构要复杂很多,这里花了我不少时间,到现在还有三个没有解决:制片国家、语言、电影别名。这三个的html的结构有点奇怪。而且BeautifulSoup处理信息的速度并不快,所以就没管它了,等下次将BeautifulSoup、xpath等工具时在来处理这个问题,这里我将他们作为一个列表反回了,方便后面的处理。
#处理详情页
def configure_infopage(bsobj):
try:
#电影排名:
movie_id=bsobj.find("span", {"class": "top250-no"}).get_text()
# 得到当前页面上的电影名称
movie_name = bsobj.find("span", {"property": "v:itemreviewed"}).get_text()
#电影简介
movie_intro=bsobj.find("span",{"property":"v:summary"}).get_text().replace("\n \u3000\u3000","").replace(" ","")
#获取电影海报页面链接
movie_photos=bsobj.find("a",{"class":"nbgnbg"}).attrs["href"]
#电影详细信息左
movie_info_items=bsobj.find("div",{"id":"info"})
#处理电影详细信息
#导演: 弗兰克·德拉邦特
movie_directer = movie_info_items.find("a", {"rel": "v:directedBy"}).get_text()
#编剧: 弗兰克·德拉邦特 / 斯蒂芬·金
movie_attrs =movie_info_items.find_all("a",{"href":re.compile("/celebrity"),})[0].get_text()+"|"+ \
movie_info_items.find_all("a", {"href": re.compile("/celebrity"), })[1].get_text()
#主演: 蒂姆·罗宾斯 / 摩根·弗里曼 / 鲍勃·冈顿 / 威廉姆·赛德勒 / 克兰西·布朗 / 吉尔·贝罗斯 / 马克·罗斯顿 / 詹姆斯·惠特摩 / 杰弗里·德曼 / 拉里·布兰登伯格 / 尼尔·吉恩托利 / 布赖恩·利比 / 大卫·普罗瓦尔 / 约瑟夫·劳格诺 / 祖德·塞克利拉 / 保罗·麦克兰尼 / 芮妮·布莱恩 / 阿方索·弗里曼 / V·J·福斯特 / 弗兰克·梅德拉诺 / 马克·迈尔斯 / 尼尔·萨默斯 / 耐德·巴拉米 / 布赖恩·戴拉特 / 唐·麦克马纳斯
# 取前3
movie_actors=""
for i in movie_info_items.find_all("a",{"rel":"v:starring"})[:5]:
movie_actors=movie_actors+i.get_text()
#类型: 剧情 / 犯罪
movie_genre=movie_info_items.find_all("span",{"property":"v:genre"})[0].get_text()+"|"+ \
movie_info_items.find_all("span", {"property": "v:genre"})[1].get_text()
#制片国家/地区: 美国
movie_saition=None
#语言: 英语
movie_language=None
#上映日期: 1994-09-10(多伦多电影节) / 1994-10-14(美国)
movie_initialReleaseDate =""
for i in movie_info_items.find_all("span", {"property": "v:initialReleaseDate"}):
movie_initialReleaseDate=movie_initialReleaseDate+i.get_text()
#片长: 142分钟
movie_Runtime=movie_info_items.find("span",{"property":"v:runtime"}).get_text()
#又名: 月黑高飞(港) / 刺激1995(台) / 地狱诺言 / 铁窗岁月 / 消香克的救赎
movie_alias=None
#IMDb链接: tt0111161
movie_IMDb_url=movie_info_items.find("a",{"rel":"nofollow"}).attrs["href"]
#电影评分信息
movie_votes_info=bsobj.find("div",{"id":"interest_sectl"})
#处理电影评分信息
#评分
movie_vote=movie_votes_info.find("strong",{"class":"ll rating_num"}).get_text()
#评分人数
movie_voters=movie_votes_info.find("span",{"span":"v:votes"})
#5星数量
movie_vote_5_stars=movie_votes_info.find_all("span",{"class":"rating_per"})[0].get_text()
print("正在解析电影信息",movie_id)
except:
print("电影信息解析失败")
return None
return [movie_id,
movie_name,
movie_vote,
movie_voters,
movie_vote_5_stars,
movie_directer,
movie_attrs,
movie_actors,
movie_genre,
movie_saition,
movie_language,
movie_initialReleaseDate,
movie_Runtime,
movie_alias,
movie_IMDb_url,
movie_intro,
movie_photos]
3.2 存储数据
#存储信息
def save_infos(infos,Directory):
filepath = Directory + "/info/"
if not os.path.exists(filepath):
os.makedirs(filepath)
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
print("-"*20)
print("正在将movie%s信息存到数据库。。。。"%(infos[0]))
cursor.execute('insert into movie_250_info \
(\
movie_id,\
movie_name,\
movie_vote,\
movie_voters,\
movie_vote_5_stars,\
movie_directer,\
movie_attrs,\
movie_actors,\
movie_genre,\
movie_saition,\
movie_language,\
movie_initialReleaseDate,\
movie_Runtime,\
movie_alias,\
movie_IMDb_url,\
movie_intro,\
movie_photos\
) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)', infos)
conn.commit()
conn.close()
print("正在将文件存储为txt。。。。。")
with open(filepath+ infos[0][3:] + ".txt", "a") as fi:
for i in infos:
if type(i) == list:
for x in i:
fi.write(x)
fi.write("\t")
continue
fi.write(i if i is not None else "None")
fi.write("\n")
print("存储完成!")
print("-" * 20)
在这一步,数据库的插入工作也废了不少时间,首先这是我第一次使用数据库,对于数据库的设计还一知半解,这也放之后单讲,今天上午我在写关于数据库的文了。应该很快了。这里我也存了一份文件作为参考。
3.3 爬取电影海报页面的30张海报的地址
可以看到,我将电影的海报地址作为列表的最后一项。这是只要用[-1]就能取到这个值。
# 获取海报页面前30张图片的地址
def get_img_url(movie_url):
html=urlopen(movie_url)
bsobj=BeautifulSoup(html, features="html.parser")
img_url=bsobj.find_all("div",{"class":"cover"})
l=[]
for i in img_url:
url=i.find("img").attrs["src"]
l.append(url)
#print("-"*20)
print("30张海报地址获取成功!")
return l
3.4下载海报
下载就很简单了,我没有用requests的保存二进制文件来保存文件,用的是urllib.urlretrieve,这个很好用。
#下载图片
def save_img(infos,Directory):
img = get_img_url(infos[-1])
filepath=Directory + "/pic/" + infos[0] + "/"
print("-" * 20)
print("海报储存在",filepath)
#print("-" * 20)
#print("\n")
for i in img:
download_img(i, filepath)
print("海报30张下载成功")
print("-" * 20)
# 下载海报
def download_img(url,download_directory):
"""
保存海报
输入:下载文件地址,和板存路径
输出:将文件保存到相应文件下
"""
if not os.path.exists(download_directory):
os.makedirs(download_directory)
file_path=download_directory+url[-14:]
try:
urlretrieve(url, file_path)
#print("1")
print("下载图片:%s完成!" % (url[-14:]))
time.sleep(1)
except :
print("下载图片:%s失败!" % (url[-14:]))
return None
我把他们分开写成了两个函数。我的老师跟我说,一个函数不应该很长。这次写爬虫中并没有注意这个问题,下次一定注意。而且我函数的分工还是有些冗余,不明确。这是一个缺点。
按照我们前面的分析,到这里就结束了。但是开头的main函数是我刚写的,之前并没这么想。
我的main函数:
def main():
Directory = "C:/Users/葛苏健/Desktop/py/python小案例/004爬取豆瓣新片"
print("正在获取10页目录。。。。。")
ten_pageUrl = get_ten_pageurl()
print("正在连接数据库。。。。。")
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
cursor.execute('create table movie_250url '
'(id int primary key, '
'name varchar(20), '
'url char(50))')
cursor.execute('create table movie_250_info (\
movie_id char(10) primary key,\
movie_name text,\
movie_vote char(20),\
movie_voters char(20),\
movie_vote_5_stars char(20),\
movie_directer text,\
movie_attrs text,\
movie_actors text,\
movie_genre text,\
movie_saition text NULL,\
movie_language text NULL,\
movie_initialReleaseDate text,\
movie_Runtime text,\
movie_alias text NULL,\
movie_IMDb_url text,\
movie_intro text,\
movie_photos text\
)')
conn.commit()
time.sleep(1)
print("连接成功!")
print("正在获取250个电影的详情页url。。。。")
movieInfoPage_queue = get_250movie_page_url(ten_pageUrl, Directory)
print("获取250个电影的详情页url成功!")
time.sleep(1)
# movieInfoPage_queue =MyQueue()
# movieInfoPage_queue.destroy()
# movieInfoPage_queue.push("https://movie.douban.com/subject/1291546/")
# # #info_path = Directory + "/info.txt"
print("正在创建文件夹")
if not os.path.exists(Directory):
os.makedirs(Directory)
print("创建成功")
print("开始爬取top250电影:")
print("#"*50)
while not movieInfoPage_queue.isEmpty():
try:
print("=" * 50)
print("正在获取页面。。。")
info = get_movie_info(movieInfoPage_queue.top())
movieInfoPage_queue.pop()
print("开始解析电影详情页。。。")
infos = configure_infopage(info)
print("解析电影详情页成功!")
save_infos(infos,Directory)
print("开始下载海报:")
save_img(infos,Directory)
print("海报下载完成\n")
print("电影%s爬取成功!"%(infos[0]))
print("=" * 50)
print("\n")
except:
#print("=" * 50)
print("失败!")
print("=" * 50)
print("\n")
continue
....废话非常多,是因为我想了解爬虫每时每刻都在做什么。
下面来总结下:
这次爬虫用到了beautifulsoup、os、re、time、urllib.urlopen、mysql.connector、urllib.urlretrieve这里个库,高级的技术也没用到。
这次学会的:数据库交互。。。其他的都只是巩固,而且这次的数据库操作也不是很复杂,数据可视化还没有完成,代理、反爬都没遇到过。。。。
但是我还是巩固了基础,对爬虫的整体架构有了更深入的理解。之后我会单独将这次用到的技术拿出来写,以此来巩固。
那再来说说这次的不足:
代码冗余,不够精简、层次不清晰、写代码前的准备不够充分、函数式编程设计模式不熟悉,没有章法。我应该去看看设计模式方面的东西——spring MVC啥的。
同时这次的爬虫速度慢、空间使用量大。我昨晚上仔细看了看python的垃圾回收机制,收获不少——引用计数、代回收Generational GC,前者用于大多数情况,后专为循环引用设计,详细见python垃圾回收
写的很好。这次的爬虫还是有点问题,250部爬下来160部,剩下的都是因为处理信息时以及电脑自身问题,爬虫是在昨天运行的,写完文章后便没理它,一直运行,结果电脑频繁睡眠,电脑的wifi一直断,还好设置了等待,不然直接出错就全凉了,后来在我自己监督下还是断了几次,所以只爬了一半多,当然我觉得大部分还是代码的问题,存数据库,数据清洗的地方有问题,因为每部电影的详情页上的信息不一样,我只参照了几个,剩下的就是格式问题,因为时间问题,仅做学习用,也就没去找那几页的差距。
好的就这样,下周末继续,下面的代码,库安装完成后可以直接运行。
全部代码:
# 爬取要求:
# 目标豆瓣top250
# 结果:电影名,排名,导演,主演,评分,年代,分类,多少人评价,一句概括,电影海报。
# 用数据库储存
# 知识点:
# 爬虫基础
# 数据库
# 用队列存储未完成的标题
# 可以考虑多进程
import time
import os
import re
import mysql.connector
#考虑使用xpath
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.request import urlretrieve
#我用的自己写的队列,模块QUEUE使用不熟练
class LNode:
def __init__(self,arg):
self.data=arg
self.next=None
class MyQueue:
#模拟队列
def __init__(self):
#phead=LNode(None)
self.data=None
self.next=None
self.front=self#指向队列首
self.rear=self#指向队列尾
#判断队列是否为空,如果为空返回True,否则返回false
def isEmpty(self):
return self.front==self.rear
#返回队列的大小
def size(self):
p=self.front
size=0
while p.next!=self.rear.next:
p=p.next
size+=1
return size
#返回队列首元素
def top(self):
if not self.isEmpty():
return self.front.next.data
else:
#print("队列为空")
return None
#返回队列尾元素
def bottom(self):
if not self.isEmpty():
return self.rear.data
else:
#print("队列为空")
return None
#出队列
def pop(self):
if self.size()==1:
data=self.front.next
self.rear=self
return data.data
elif not self.isEmpty():
data=self.front.next
self.front.next=self.front.next.next
#print("出队列成功")
return data.data
else:
#print("队列已为空")
return None
#入队列
def push(self,item):
tmp=LNode(item)
self.rear.next=tmp
self.rear=self.rear.next
#print("入队列成功")
#清空队列
def destroy(self):
self.next=None
#print("队列已清空")
#打印队列
def showQueue(self):
if not self.isEmpty():
p=self.front.next
while p != self.rear.next:
print(p.data)
p=p.next
#获得10页包含250个简介的页面地址
def get_ten_pageurl():
#array=["https://movie.douban.com/top250?start=0&filter="]
array=[]
for i in range(0,250,25):
array.append("https://movie.douban.com/top250?start="+str(i)+"&filter=")
return array
# 得到每一个电影的详情页地址
def get_250movie_page_url(ten_pageurl,Directory):
"""
输入10页地址
将top250 的电影首页地址保存下来,同时存到队列中和本地text
"""
if not os.path.exists(Directory):
os.makedirs(Directory)
url_queue=MyQueue()
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
conn.commit()
for page_url in ten_pageurl:
try:
html = urlopen(page_url)
bsobj = BeautifulSoup(html, features="html.parser")
# 得到当前页面上25个包含序号、详情页的地址的div标签,存为列表
movie_info_items = bsobj.findAll("div", {"class": "pic"})
for movie_info in movie_info_items:
try:
movie_id = int(movie_info.find("em").get_text())
movie_info_url = movie_info.find("a").attrs["href"]
movie_name=movie_info.find("img").attrs["alt"]
url_queue.push(movie_info_url)
with open(Directory + "/250homepage_url.txt", "a") as f:
f.write(str(movie_id))
f.write("\t")
f.write(movie_name)
f.write("\t")
f.write(movie_info_url)
f.write("\n")
print("获取movie%s详情页url成功"%(movie_id))
cursor.execute('insert into movie_250url '
'(id, name,url) '
'values (%s, %s,%s)',
[movie_id, movie_name, movie_info_url])
conn.commit()
except:
print("获取movie详情页url失败!")
#continue
time.sleep(2)
except:
print("页面%s处理失败"%(page_url))
time.sleep(1)
continue
cursor.close()
return url_queue
#获取详情页
def get_movie_info(url):
try:
html = urlopen(url)
bsobj = BeautifulSoup(html, features="html.parser")
print("获取详情页面成功",url[-8:])
time.sleep(1)
return bsobj
except:
print("详情页面%s获取失败"%(url[-8:]))
time.sleep(1)
return None
#处理详情页
def configure_infopage(bsobj):
try:
#电影排名:
movie_id=bsobj.find("span", {"class": "top250-no"}).get_text()
# 得到当前页面上的电影名称
movie_name = bsobj.find("span", {"property": "v:itemreviewed"}).get_text()
#电影简介
movie_intro=bsobj.find("span",{"property":"v:summary"}).get_text().replace("\n \u3000\u3000","").replace(" ","")
#获取电影海报页面链接
movie_photos=bsobj.find("a",{"class":"nbgnbg"}).attrs["href"]
#电影详细信息左
movie_info_items=bsobj.find("div",{"id":"info"})
#处理电影详细信息
#导演: 弗兰克·德拉邦特
movie_directer = movie_info_items.find("a", {"rel": "v:directedBy"}).get_text()
#编剧: 弗兰克·德拉邦特 / 斯蒂芬·金
movie_attrs =movie_info_items.find_all("a",{"href":re.compile("/celebrity"),})[0].get_text()+"|"+ \
movie_info_items.find_all("a", {"href": re.compile("/celebrity"), })[1].get_text()
#主演: 蒂姆·罗宾斯 / 摩根·弗里曼 / 鲍勃·冈顿 / 威廉姆·赛德勒 / 克兰西·布朗 / 吉尔·贝罗斯 / 马克·罗斯顿 / 詹姆斯·惠特摩 / 杰弗里·德曼 / 拉里·布兰登伯格 / 尼尔·吉恩托利 / 布赖恩·利比 / 大卫·普罗瓦尔 / 约瑟夫·劳格诺 / 祖德·塞克利拉 / 保罗·麦克兰尼 / 芮妮·布莱恩 / 阿方索·弗里曼 / V·J·福斯特 / 弗兰克·梅德拉诺 / 马克·迈尔斯 / 尼尔·萨默斯 / 耐德·巴拉米 / 布赖恩·戴拉特 / 唐·麦克马纳斯
# 取前3
movie_actors=""
for i in movie_info_items.find_all("a",{"rel":"v:starring"})[:5]:
movie_actors=movie_actors+i.get_text()
#类型: 剧情 / 犯罪
movie_genre=movie_info_items.find_all("span",{"property":"v:genre"})[0].get_text()+"|"+ \
movie_info_items.find_all("span", {"property": "v:genre"})[1].get_text()
#制片国家/地区: 美国
movie_saition=None
#语言: 英语
movie_language=None
#上映日期: 1994-09-10(多伦多电影节) / 1994-10-14(美国)
movie_initialReleaseDate =""
for i in movie_info_items.find_all("span", {"property": "v:initialReleaseDate"}):
movie_initialReleaseDate=movie_initialReleaseDate+i.get_text()
#片长: 142分钟
movie_Runtime=movie_info_items.find("span",{"property":"v:runtime"}).get_text()
#又名: 月黑高飞(港) / 刺激1995(台) / 地狱诺言 / 铁窗岁月 / 消香克的救赎
movie_alias=None
#IMDb链接: tt0111161
movie_IMDb_url=movie_info_items.find("a",{"rel":"nofollow"}).attrs["href"]
#电影评分信息
movie_votes_info=bsobj.find("div",{"id":"interest_sectl"})
#处理电影评分信息
#评分
movie_vote=movie_votes_info.find("strong",{"class":"ll rating_num"}).get_text()
#评分人数
movie_voters=movie_votes_info.find("span",{"span":"v:votes"})
#5星数量
movie_vote_5_stars=movie_votes_info.find_all("span",{"class":"rating_per"})[0].get_text()
print("正在解析电影信息",movie_id)
except:
print("电影信息解析失败")
return None
return [movie_id,
movie_name,
movie_vote,
movie_voters,
movie_vote_5_stars,
movie_directer,
movie_attrs,
movie_actors,
movie_genre,
movie_saition,
movie_language,
movie_initialReleaseDate,
movie_Runtime,
movie_alias,
movie_IMDb_url,
movie_intro,
movie_photos]
# 获取海报页面前30张图片的地址
def get_img_url(movie_url):
html=urlopen(movie_url)
bsobj=BeautifulSoup(html, features="html.parser")
img_url=bsobj.find_all("div",{"class":"cover"})
l=[]
for i in img_url:
url=i.find("img").attrs["src"]
l.append(url)
#print("-"*20)
print("30张海报地址获取成功!")
return l
# 下载海报
def download_img(url,download_directory):
"""
保存海报
输入:下载文件地址,和板存路径
输出:将文件保存到相应文件下
"""
if not os.path.exists(download_directory):
os.makedirs(download_directory)
file_path=download_directory+url[-14:]
try:
urlretrieve(url, file_path)
#print("1")
print("下载图片:%s完成!" % (url[-14:]))
time.sleep(1)
except :
print("下载图片:%s失败!" % (url[-14:]))
return None
#处理信息
def save_infos(infos,Directory):
filepath = Directory + "/info/"
if not os.path.exists(filepath):
os.makedirs(filepath)
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
print("-"*20)
print("正在将movie%s信息存到数据库。。。。"%(infos[0]))
cursor.execute('insert into movie_250_info \
(\
movie_id,\
movie_name,\
movie_vote,\
movie_voters,\
movie_vote_5_stars,\
movie_directer,\
movie_attrs,\
movie_actors,\
movie_genre,\
movie_saition,\
movie_language,\
movie_initialReleaseDate,\
movie_Runtime,\
movie_alias,\
movie_IMDb_url,\
movie_intro,\
movie_photos\
) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)', infos)
conn.commit()
conn.close()
print("正在将文件存储为txt。。。。。")
with open(filepath+ infos[0][3:] + ".txt", "a") as fi:
for i in infos:
if type(i) == list:
for x in i:
fi.write(x)
fi.write("\t")
continue
fi.write(i if i is not None else "None")
fi.write("\n")
print("存储完成!")
print("-" * 20)
#下载图片
def save_img(infos,Directory):
img = get_img_url(infos[-1])
filepath=Directory + "/pic/" + infos[0] + "/"
print("-" * 20)
print("海报储存在",filepath)
#print("-" * 20)
#print("\n")
for i in img:
download_img(i, filepath)
print("海报30张下载成功")
print("-" * 20)
def main():
Directory = "C:/Users/葛苏健/Desktop/py/python小案例/004爬取豆瓣新片"
print("正在获取10页目录。。。。。")
ten_pageUrl = get_ten_pageurl()
print("正在连接数据库。。。。。")
conn = mysql.connector.connect(user='root', password='password', database='douban250')
cursor = conn.cursor()
cursor.execute('create table movie_250url '
'(id int primary key, '
'name varchar(20), '
'url char(50))')
cursor.execute('create table movie_250_info (\
movie_id char(10) primary key,\
movie_name text,\
movie_vote char(20),\
movie_voters char(20),\
movie_vote_5_stars char(20),\
movie_directer text,\
movie_attrs text,\
movie_actors text,\
movie_genre text,\
movie_saition text NULL,\
movie_language text NULL,\
movie_initialReleaseDate text,\
movie_Runtime text,\
movie_alias text NULL,\
movie_IMDb_url text,\
movie_intro text,\
movie_photos text\
)')
conn.commit()
time.sleep(1)
print("连接成功!")
print("正在获取250个电影的详情页url。。。。")
movieInfoPage_queue = get_250movie_page_url(ten_pageUrl, Directory)
print("获取250个电影的详情页url成功!")
time.sleep(1)
# movieInfoPage_queue =MyQueue()
# movieInfoPage_queue.destroy()
# movieInfoPage_queue.push("https://movie.douban.com/subject/1291546/")
# # #info_path = Directory + "/info.txt"
print("正在创建文件夹")
if not os.path.exists(Directory):
os.makedirs(Directory)
print("创建成功")
print("开始爬取top250电影:")
print("#"*50)
while not movieInfoPage_queue.isEmpty():
try:
print("=" * 50)
print("正在获取页面。。。")
info = get_movie_info(movieInfoPage_queue.top())
movieInfoPage_queue.pop()
print("开始解析电影详情页。。。")
infos = configure_infopage(info)
print("解析电影详情页成功!")
save_infos(infos,Directory)
print("开始下载海报:")
save_img(infos,Directory)
print("海报下载完成\n")
print("电影%s爬取成功!"%(infos[0]))
print("=" * 50)
print("\n")
except:
#print("=" * 50)
print("失败!")
print("=" * 50)
print("\n")
continue
if __name__ == '__main__':
main()
可以做做修改,提高效率,然后将存数据库和提取信息的地方的容错率提高一点,这样每一页就都能保存下来了。下周寻个有点难度但是内容不要太多。
加油!最近在复习计算机网络——tcp/ip——应用层->运输层->网络层->数据链路层->物理层,这里的应用层和运输层之间还是不太理解。
。。。。。
2019年3月17日17:04:44
网友评论