Python有着丰富的网络库,因此在做数据收集方面有着无可比拟的优势。以上的话是商业吹一波,当不得真(现在很多库都有多语言版本)。
一个爬虫基本上都可以划分为三个部分:数据获取,数据处理,数据持久化。我们会以爬取豆瓣电影250为例子,每一个模块对应一个函数,一步一步来获得我们想要的数据。
数据获取
Python为我们提供了几个常用的模拟get、post请求的库,例如urllib、requests等等。来看个简单的例子
from urllib import request
#构造一个Get请求
with request.urlopen('https://movie.douban.com/top250') as f:
#读取Response中的数据部分
data = f.read()
#请求状态
print('Status:', f.status, f.reason)
#获取Response Headers
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', data.decode('utf-8'))'
这里获取了豆瓣电影250的数据,包括所有的response headers和data。如果你不熟悉html协议,可以运行一下上面的程序,看一看我们得到的数据是什么样子。这就是最简单的爬虫,我们构造了一个请求,得到了请求对应的资源。怎么样,是不是很轻松。
这里隐藏了一个问题:我们现在的程序都是爬取的是单页面数据,不能够获得完整的数据。我们将在后序解决这个问题。
数据处理
上面的程序还有个问题,data其实是一个html格式的数据,含有我们不需要的标签信息,需要对数据做一些处理,让数据能够聚焦在有价值的信息上。
数据处理有两种方式,一种是正则匹配,有兴趣的同学可以搜索相关信息;还有一种就是我想要介绍的BeautifulSoup文本解析工具,它是一个用于解析Html和Xml的Python库。
def parse_html(html):
soup = BeautifulSoup(html,"html.parser")
movie_list_soup = soup.find('ol', {'class': 'grid_view'})
movie_name_list = []
for movie_li in movie_list_soup.find_all('li'):
detail = movie_li.find('div', {'class': 'hd'})
movie_name = detail.find('span', {'class': 'title'}).getText()
movie_name_list.append(movie_name)
return movie_name_list
对比已经获得的html格式的数据,可以很清晰的看到BeautifulSoup如何找到我们想要的数据。
<ol class="grid_view">
<li>
<div class="item">
<div class="pic">
<em class="">1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救赎" src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.jpg" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title">肖申克的救赎</span>
<span class="title"> / The Shawshank Redemption</span>
<span class="other"> / 月黑高飞(港) / 刺激1995(台)</span>
</a>
...
BeautifulSoup首先定位到class属性为grid_view的ol标签,该标签下面每一个li标签内都是一个电影的数据。找到li标签中的数据后,定位class为hd的div标签,最后找到class为title的span标签,并提取其中的内容。
数据持久化
数据处理完后,就可以对数据做持久化操作,本例中我们把数据保存在txt文件中。
def save_data(data):
with open('topMoive.txt', 'w', encoding='utf8') as movie_list:
movie_list.write('\n'.join(data))
整合程序
def main():
url = 'https://movie.douban.com/top250'
data = download_page(url)
print(data.decode('utf-8'))
movie_list = parse_html(data)
save_data(movie_list)
if __name__ == '__main__':
main()
这里就不多说了,依次调用数据获取,数据解析,数据持久化三个函数就可以。
多页面爬取
这是一个比较复杂的问题,多页面的爬取在算法层面其实相当于图的遍历。如果你没有遗忘的话,图的遍历总会涉及到快速查找某节点是否已经被访问过,这在爬虫系统里面就涉及到url去重的问题。此外,如果url数量实在是太多,为了提升性能,还会涉及到分布式爬虫系统,其实就是准备一个队列,由多个机器去读取里面的url而已。
幸运的是,在"爬取豆瓣电影Top250"这个命题下面,我们无需面对上面的问题。下面开始改造我们的程序吧。
<span class="next">
<link rel="next" href="?start=25&filter="/>
<a href="?start=25&filter=" >后页></a>
</span>
上面一段程序,是从html中摘取的片段,我们发现下一页其实已经通过链接给我们了,只要重复获得该链接我们就可以不断获得新的电影信息。于是我们在解析数据环节获取下一页的链接,并返回该链接。
def parse_html(html):
soup = BeautifulSoup(html,"html.parser")
movie_list_soup = soup.find('ol', {'class': 'grid_view'})
movie_name_list = []
for movie_li in movie_list_soup.find_all('li'):
detail = movie_li.find('div', {'class': 'hd'})
movie_name = detail.find('span', {'class': 'title'}).getText()
movie_name_list.append(movie_name)
next_page = soup.find('span', {'class': 'next'}).find('a')
if next_page:
return movie_name_list, URL_CONST + next_page['href']
return movie_name_list, None
在main中,我们要启动一个循环,直到返回的下一页链接为空。
URL_CONST = 'https://movie.douban.com/top250'
def main():
url = URL_CONST
movie_list = []
while url:
data = download_page(url)
movie_list_tmp, url = parse_html(data)
print('\n'.join(movie_list_tmp))
movie_list.extend(movie_list_tmp)
save_data(movie_list)
至此我们实现了简单的多页面爬虫,完整的程序参见文末。后面一章,我们会讲一下如何绕过登录去获取数据,会涉及到selenium、requests等python库。
from bs4 import BeautifulSoup
from urllib import request
def download_page(url):
with request.urlopen(url) as f:
data = f.read()
return data
URL_CONST = 'https://movie.douban.com/top250'
def main():
url = URL_CONST
movie_list = []
while url:
data = download_page(url)
movie_list_tmp, url = parse_html(data)
print('\n'.join(movie_list_tmp))
movie_list.extend(movie_list_tmp)
save_data(movie_list)
def parse_html(html):
soup = BeautifulSoup(html,"html.parser")
movie_list_soup = soup.find('ol', {'class': 'grid_view'})
movie_name_list = []
for movie_li in movie_list_soup.find_all('li'):
detail = movie_li.find('div', {'class': 'hd'})
movie_name = detail.find('span', {'class': 'title'}).getText()
movie_name_list.append(movie_name)
next_page = soup.find('span', {'class': 'next'}).find('a')
if next_page:
return movie_name_list, URL_CONST + next_page['href']
return movie_name_list, None
def save_data(data):
with open('topMoive.txt', 'w', encoding='utf-8') as movie_list:
movie_list.write('\n'.join(data))
if __name__ == '__main__':
main()