说明
最近看了下爬虫基础,想写个博客来记录一下,一来是可以方便和我一样刚入门的小白来参考学习,二来也当做自己的笔记供自己以后查阅。
本文章是利用python3.6和Requests库(需自行安装,cmd里运行pip install requests)以及正则表达式(其实利用正则表达式是比较麻烦的一种方式,但是正则表达式在很多语言都有应用,练习一下也是有好处的)来爬取豆瓣影评Top250的文本信息以及然后存储在本地的文件当中,阅读本文章前建议学习一下python基础和正则表达式。
第一次写博客而且刚刚入门python以及爬虫基础,本文的思路仅适用于不需要登录而且没有反爬机制的HTML网页,过程当中难免会有错误,还望大佬们能指出!
抓取分析
1、首先打开需要爬取的网址:https://movie.douban.com/top250 ,可以看到影评靠前的电影的详细信息。
2、向下滚动点击一次后页发现url变为https://movie.douban.com/top250?start=25&filter= ,再次向下滚动点击后页url又变为
https://movie.douban.com/top250?start=50&filter=,可以看出start参数的不同,分别为25和50,因此可以猜测后面url的start参数为75,100……利用这条规律就可以将url统一起来然后进行爬取
3、利用Requests下载目标网页的HTML代码
import requests
from requests.exceptions import RequestException
def get_one_page(url):
try:
res=requests.get(url)
if res.status_code==200:
return res.text
return None
except RequestException:
return None
html=get_one_page('https://movie.douban.com/top250')
print(html)
本段代码是先用import导入程序必要的包,定义了一个函数get_one_page,参数为目标url,调用这个函数会返回目标url的html文本。函数内部利用requests来下载网页还是非常简单的,requests利用了get方法请求了目标网页,返回response对象,如果状态码是200(请求成功),就返回respose的text文本信息,利用print进行打印测试
4、得到目标网页的HTML代码之后,利用正则表达式进行解析。
查看网页源代码,可以发现每条电影的信息都在ol标签的li标签下
接下来就是利用正则表达式对html文档进行解析了,在此我们设定爬取目标为每个电影的排名,名称,描述,星级,评价人数以及主题等信息。
首先可以看到肖申克的救赎这部电影的排名是在该li标签下的em标签内,提取排名的正则表达式:
<em class=" ">(\d+)</em>
关于正则表达式,可以利用在线测试工具http://tool.oschina.net/regex/#来测试自己写出的正则表达式是否正确,如果在在线测试工具里测试含有换行符的字符串进行正则匹配会匹配不到的(可能是因为我的知识水平不够哈,在python中可以添加re.S参数进行解决,因为一个点可以匹配除除了换行符以外的其他任意字符)这里仅对相关的一些正则的语法进行解释:
\d表示数字匹配,加上括号表示要进行提取的部分
提取电影名称的正则表达式:
<span class="title">(.*?)</span> #注意星号前面有个英文的点,这个点表示任意字符(换行符除外),型号表示任意长度,问号表示以非贪婪匹配的方式来匹配字符串。
关于贪婪匹配:
比如说给定字符串 hello54188hahaha 给定正则表达式 ^h.*(\d+)hahaha$以及 ^h.*?(\d+)hahaha$
# ^h表示查找以字母h开头的字符串,hahaha$表示以hahaha结尾的字符串,\d+表示匹配至少一个长度的数字
因为\d+代表提取出来至少长度为1的数字,所以第一种贪婪匹配的方式会把5418算进在.*中去,第一种方式得到的匹配出的数字的结果是8,而第二种非贪婪匹配的方式是把54188都算在\d+当中去的,所以第二中方式匹配除的结果为54188。所以说,你明白我的意思吧?
之后再对描述,星级,评价人数以及电影主演进行正则提取,在此都是同样的原理,不再一一赘述,最后匹配出的综合正则表达式为:
<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>
利用该正则表达式对下载的HTML网页进行解析
import requests
import re
from requests.exceptions import RequestException
def get_one_page(url):
try:
res=requests.get(url)
if res.status_code==200:
return res.text
return None
except RequestException:
return None
def parse_one_html(html):
regex='<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
pattern=re.compile(regex,re.S)
items=re.findall(pattern,html)
print(items)
def main(url):
html=get_one_page(url)
parse_one_html(html)
main('https://movie.douban.com/top250')
在此又新定义了一个parse_one_html()函数,传入参数为html字符串,利用re模块的compile函数会对正则字符串进行封装,传入参数re.S使得该正则对象能够有效的解析出换行字符串,前面提到过正常情况下一个点只能匹配出除了换行字符的任意字符,最终会解析除如下list列表(只列举出一条数据):
[('1', '肖申克的救赎', '\n 导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br>\n 1994 / 美国 / 犯罪 剧情\n ', '9.6', '1170822人评价', '希望让人自由。')]
这样虽然得到了想要的结果,但是得到的数据比较杂乱,将匹配结果进一步进行处理,这里对"描述“里面的特殊字符 和<br>进行处理,利用re模块的sub函数将其替换为空字符串
#对得到的list列表进行处理
for item in items:
content=""
for every_list in item[2].split():
content=content+"".join(every_list)
content=re.sub(' ',' ',content)
content=re.sub('<br>', '', content)
print(content)
得到描述的内容打印如下:
导演:弗兰克·德拉邦特FrankDarabont 主演:蒂姆·罗宾斯TimRobbins/...1994 / 美国 / 犯罪剧情
导演:陈凯歌KaigeChen 主演:张国荣LeslieCheung/张丰毅FengyiZha...1993 / 中国大陆香港 / 剧情爱情同性
导演:吕克·贝松LucBesson 主演:让·雷诺JeanReno/娜塔莉·波特曼...1994 / 法国 / 剧情动作犯罪
导演:RobertZemeckis 主演:TomHanks/RobinWrightPenn/GarySinise1994 / 美国 / 剧情爱情
导演:罗伯托·贝尼尼RobertoBenigni 主演:罗伯托·贝尼尼RobertoBeni...1997 / 意大利 / 剧情喜剧爱情战争
导演:詹姆斯·卡梅隆JamesCameron 主演:莱昂纳多·迪卡普里奥Leonardo...1997 / 美国 / 剧情爱情灾难
导演:宫崎骏HayaoMiyazaki 主演:柊瑠美RumiHîragi/入野自由Miy...2001 / 日本 / 剧情动画奇幻
导演:史蒂文·斯皮尔伯格StevenSpielberg 主演:连姆·尼森LiamNeeson...1993 / 美国 / 剧情历史战争
导演:克里斯托弗·诺兰ChristopherNolan 主演:莱昂纳多·迪卡普里奥Le...2010 / 美国英国 / 剧情科幻悬疑冒险
导演:安德鲁·斯坦顿AndrewStanton 主演:本·贝尔特BenBurtt/艾丽...2008 / 美国 / 爱情科幻动画冒险
导演:莱塞·霍尔斯道姆LasseHallström 主演:理查·基尔RichardGer...2009 / 美国英国 / 剧情
导演:拉库马·希拉尼RajkumarHirani 主演:阿米尔·汗AamirKhan/卡...2009 / 印度 / 剧情喜剧爱情歌舞
导演:朱塞佩·托纳多雷GiuseppeTornatore 主演:蒂姆·罗斯TimRoth/...1998 / 意大利 / 剧情音乐
导演:克里斯托夫·巴拉蒂ChristopheBarratier 主演:杰拉尔·朱诺Géra...2004 / 法国瑞士德国 / 剧情音乐
导演:刘镇伟JeffreyLau 主演:周星驰StephenChow/吴孟达ManTatNg...1995 / 香港中国大陆 / 喜剧爱情奇幻冒险
导演:彼得·威尔PeterWeir 主演:金·凯瑞JimCarrey/劳拉·琳妮Lau...1998 / 美国 / 剧情科幻
导演:弗朗西斯·福特·科波拉FrancisFordCoppola 主演:马龙·白兰度M...1972 / 美国 / 剧情犯罪
导演:宫崎骏HayaoMiyazaki 主演:日高法子NorikoHidaka/坂本千夏Ch...1988 / 日本 / 动画奇幻冒险
导演:克里斯托弗·诺兰ChristopherNolan 主演:马修·麦康纳MatthewMc...2014 / 美国英国加拿大冰岛 / 剧情科幻冒险
导演:黄东赫Dong-hyukHwang 主演:孔侑YooGong/郑有美Yu-miJeong...2011 / 韩国 / 剧情
导演:刘伟强/麦兆辉 主演:刘德华/梁朝伟/黄秋生2002 / 香港 / 剧情犯罪悬疑
导演:奥利维·那卡什OlivierNakache/艾力克·托兰达EricToledano 主...2011 / 法国 / 剧情喜剧
导演:加布里尔·穆奇诺GabrieleMuccino 主演:威尔·史密斯WillSmith...2006 / 美国 / 剧情传记家庭
导演:维克多·弗莱明VictorFleming/乔治·库克GeorgeCukor 主演:费...1939 / 美国 / 剧情历史爱情战争
导演:罗伯·莱纳RobReiner 主演:玛德琳·卡罗尔MadelineCarroll/卡...2010 / 美国 / 剧情喜剧爱情
Process finished with exit code 0
将得到的新的描述以及爬取到的其他内容信息存储到dict字典当中去
#对得到的list列表进行处理
for item in items:
content=""
for every_list in item[2].split():
content=content+"".join(every_list)
content=re.sub(' ',' ',content)
content=re.sub('<br>', '', content)
print(content)
#将获取到的信息存储到
dict={
"number":item[0],
"name":item[1],
"describe":content,
"star":item[3],
"evaluate":item[4],
"title":item[5]
}
print(dict)
打印出dict结果如下,可以看出来经过处理后的结果比较整齐规范
接下来就是要利用json库将dict内的数据存储到本地文件当中去,在此先导入json模块 import json,在for循环里面接下来写入如下代码,以utf-8编码追加的方式打开本地txt并循环将每条记录写入txt文件当中
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(dict,ensure_ascii=False)+'\n')
至此首页的25条记录写入本地txt文件完成
因为我们要爬取的是250条记录,所以一共要遍历10个url进行爬取,前面已经说过每个url只是start参数不同,因此需要给主函数传递一个start函数,调用10次主函数得到完整的250条数据。
def main(start):
url='https://movie.douban.com/top250'
if start!=0:
url='https://movie.douban.com/top250?start='+str(start)+'&filter='
html=get_one_page(url)
parse_one_html(html)
if __name__=='__main__':
for i in range(10):
main(i*25)
在主函数内根据传入的start参数不同,请求的url也不同,然后调用10次主函数传入不同的参数就可以发现在本地文件中存储了250条记录。
完整代码如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import re
import json
from requests.exceptions import RequestException
#得到网页源代码
def get_one_page(url):
try:
res=requests.get(url)
if res.status_code==200:
return res.text
return None
except RequestException:
return None
#对源代码进行解析将要获取的数据存储到dict当中去然后写入本地文件
def parse_one_html(html):
regex='<em class="">(\d+)</em>.*?<span class="title">(.*?)</span>.*?<p class="">(.*?)</p>.*?<span class="rating_num" property="v:average">(.*?)</span>.*?<span>(.*?)</span>.*?<span class="inq">(.*?)</span>'
pattern=re.compile(regex,re.S)
items=re.findall(pattern,html)
#对得到的list列表进行处理,将每条记录变成一个dict
for item in items:
content=""
for every_list in item[2].split():
content=content+"".join(every_list)
content=re.sub(' ',' ',content)
content=re.sub('<br>', '', content)
#将获取到的信息存储到dict字典当中
dict={
"number":item[0],
"name":item[1],
"describe":content,
"star":item[3],
"evaluate":item[4],
"title":item[5]
}
#将得到的dict写入本地文件当中去
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(dict,ensure_ascii=False)+'\n')
def main(start):
url='https://movie.douban.com/top250'
if start!=0:
url='https://movie.douban.com/top250?start='+str(start)+'&filter='
html=get_one_page(url)
parse_one_html(html)
if __name__=='__main__':
for i in range(10):
main(i*25)
所以说,第一篇博客完成了!