本专栏是以杨秀璋老师爬虫著作《Python网络数据爬取及分析「从入门到精通」》为主线、个人学习理解为主要内容,以学习笔记形式编写的。
本专栏不光是自己的一个学习分享,也希望能给您普及一些关于爬虫的相关知识以及提供一些微不足道的爬虫思路。
专栏地址:Python网络数据爬取及分析「从入门到精通」
更多爬虫实例详见专栏:Python爬虫牛刀小试
前文回顾:
「Python爬虫系列讲解」一、网络数据爬取概述
「Python爬虫系列讲解」二、Python知识初学
「Python爬虫系列讲解」三、正则表达式爬虫之牛刀小试
「Python爬虫系列讲解」四、BeautifulSoup 技术
目录
上一讲详细介绍了 BeautifulSoup 技术,本文将结合具体实例进行深入分析。具体而言,本文讲述一个基于 BeautifulSoup 技术的爬虫,用于爬取豆瓣排名前 250 名电影的信息,主要内容包括:分析网页 DOM 树结构、爬取豆瓣电影信息、分析链接跳转及爬取每部电影对应的详细信息。
1 分析网页 DOM 树结构
1.1 分析网页结构及简单爬取
确定目标网页 url 地址:https://movie.douban.com/top250?format=text
由上图可见,豆瓣排名前 250 名电影中部分电影的信息,包括电影中文名称、英文名称、导演、主演、评分、评论数等信息,接下来需要对其进行 DOM 树结构分析。
HTML 网页是以标签对的形式出现的,这种标签对呈树形结构显示,通常称为 DOM 树结构。
首先要对目标页面进行元素分析,比如这里所说的豆瓣电影网站,邮寄选择“检查”或按下键盘 F12 键查看。
通过点击元素选择器 “” 我们发现,想要的目标信息全在 <div class="item"> </div> 路径下的 <li> </li> 标签对里。其中,电影《肖申克的救赎》的 HTML 中对应的内容为 <li> <div class="item">……</div> </li> ,因此可以通过 class 值为“item” 来定位电影的信息。调用 BeautifulSoup 扩展库的 find_all(attrs={"class": "item"}) 函数获取其信息。
下面这段代码可以获取电影的信息,调用 BeautifulSoup 中的 find_all() 函数可以获取“<div class='item'>”的信息。
import requests
from bs4 import BeautifulSoup
# 爬虫函数
def crawl(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
html = requests.get(url, headers=headers).text
# lxml:html解析库(把HTML代码转化成Python对象)
soup = BeautifulSoup(html, 'lxml')
print("豆瓣电影250:序号 \t 影片名 \t 评分 \t 评价人数")
for tag in soup.find_all(attrs={"class": "item"}):
content = tag.get_text()
content = content.replace('\n', '') # 删除多余换行
print(content, '\n')
# 主函数
if __name__=='__main__':
url = 'https://movie.douban.com/top250?format=text'
crawl(url)
1.2 定位节点及网页反页分析
前面用代码实现了获取电影简介的信息,但是这些信息是融合在一起的,而在数据分析时,通常需要将某些具有使用价值的信息提取出来,并存储至数组、列表或数据库中,比如电影名称、演员信息、电影评分等。
这里有两种常见的信息供大家参考:
- 文本分析。从获取的电影简介文本信息中提取某些特定的值,通常采用字符串处理方法进行提取。
- 节点定位。在写爬虫的过程中定位相关节点,然后进行爬取所需节点的操作,最后赋值给变量或存储到数据库中。
像这样一对应,就会很轻易地查看到比如“评价人数”等数据在节点中的位置。
获取节点的核心代码如下,定位 class 属性为 “item” 的 div 布局后,再调用 find_all() 函数查找 class 属性为 “title” 的标签,并获取第一个值输出。接着调用 find() 函数爬取评分信息,通过 get_text() 函数获取内容。
for tag in soup.find_all(attrs={"class": "item"}):
title = tag.find_all(attrs={"class": "title"}) # 电影名称
info = tag.find(attrs={"class": "star"}).get_text() # 爬取评分和评论数
print(title[0])
print(info.replace('\n', ''))
讲到这里,第一页的 25 部电影就爬取成功了,而这样的网页共 10 页 ,每页显示 25 部电影,那么如何获取这250部电影的网证信息呢?这就涉及到了链接跳转和网页的翻页分析。
网页的翻页分析通常有 3 种方法:
- 单击“后页”按钮分析 URL 网址,然后分析他们之间的规律。利用这种方法的网站通常采用 GET 方法进行传值,而有些网站采用局部刷新技术,翻页后的 URL 仍然不变。
- 获取“后页”按钮或页码的超链接,然后依次调用 urllib2.urlopen(url) 函数来访问 URL 并实现网页跳转。
- 采用网页自动操作技术,获取“后页”按钮或超链接进行自动单击跳转,如 Selenium 技术中的戍边单击事件。
通过单击上图中的 “2”、“3”、“10” ,可以看到网页的 URL 的变化如下:
第 2 页:https://movie.douban.com/top250?start=25&filter=
第 3 页:https://movie.douban.com/top250?start=50&filter=
第10页:https://movie.douban.com/top250?start=225&filter=
网页 URL 变化是有一定规律的,“ top250? star = 25 ”表示获取第 2 页(序号为 26~50 号)的电影信息;“ top250? star = 50 ”表示获取第 2 页(序号为 51~75 号)的电影信息,以此类推。这里写一个循环即可获取 250 部电影的完整信息。
i = 0
while i < 10:
num = i * 25 # 每次显示 25 部,URL 序号按 25 增加
urls = 'https://movie.douban.com/top250?start=' + str(num) + '&filter='
crawl(urls)
当 i 的初始值为 0,num 值为 0 时,获取第 1 页信息;当 i 增加为 1,num 值为 25 时,获取第 2 页信息;当增加为 9,num 值为 225 时,获取第 10 页的信息。
2 爬取豆瓣电影信息
首先给出完整代码:
import urllib.request
import re
import requests
from bs4 import BeautifulSoup
import codecs
# 爬虫函数
def crawl(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
html = requests.get(url, headers=headers).text
# lxml:html解析库(把HTML代码转化成Python对象)
soup = BeautifulSoup(html, 'lxml')
infofile.write("")
print('爬取豆瓣电影:\n')
for tag in soup.find_all(attrs={"class": "item"}):
# 爬取序号
num = tag.find('em').get_text()
print(num)
infofile.write(num + "\r\n")
# 电影名称
name = tag.find_all(attrs={"class": "title"})
zwname = name[0].get_text()
print('[中文名称]', zwname)
infofile.write("[中文名称]" + zwname + "\r\n")
# 网页链接
url_movie = tag.find(attrs={"class": "hd"}).a
urls = url_movie.attrs['href']
print('[网页链接]', urls)
infofile.write("[网页链接]" + urls + "\r\n")
# 爬取评分和评论数
info = tag.find(attrs={"class": "star"}).get_text()
info = info.replace('\n', '')
info = info.lstrip()
print('[评分评论]', info)
# 获取评语
info = tag.find(attrs={"class": "inq"})
if(info): # 避免没有影评时调用 get_text() 报错
content = info.get_text()
print('[影评]', content)
infofile.write("[影评]" + content + "\r\n")
print('')
# 主函数
if __name__=='__main__':
infofile = codecs.open("Result_Douban.txt", 'a', 'utf-8')
i = 0
while i < 10:
print('页码:', i+1)
num = i * 25 # 每次显示 25 部,URL 序号按 25 增加
urls = 'https://movie.douban.com/top250?start=' + str(num) + '&filter='
crawl(urls)
infofile.write("\r\n\r\n")
i = i + 1
infofile.close()
2.1 获取序号
序号对应的 HTML 源码如下图所示,需要定位到“<em class> 1 </em>” 节点,通过 find('em') 函数获取具体内容。
获取代码如下:
num = tag.find('em').get_text()
2.2 获取电影名称
电影名称(包括中文名称和英文名称)在 “<class=title>” 中,而电影其他名称则在 “<class="other>” 中。
因为 HTML 中包含两个 title,即“<span class="title> </span>”,所以使用 tag.find_all(attrs={"class": "title"}) 代码获得了两个标题,但这里仅需要中文标题,故直接通过变量 name[0] 获取其第一个值,即为中文名称,在调用 get_text() 函数用于获取其内容。
name = tag.find_all(attrs={"class": "title"})
zwname = name[0].get_text()
print('[中文名称]', zwname)
infofile.write("[中文名称]" + zwname + "\r\n")
上述代码调用了 codes 库进行文件处理,其中文件操作的核心代码如下。打开文件的 3 各参数分别是:文件名、读/写方式、编码方式。此处文件名为“Result_Douban.txt”,采用文件写方式(a),编码方式为“utf-8”。
infofile = codecs.open("Result_Douban.txt", 'a', 'utf-8') # 打开文件
infofile.write(num + " " + content + "\r\n") # 写文件
infofile.close() # 关闭文件
2.3 获取电影链接
url_movie = tag.find(attrs={"class": "hd"}).a
urls = url_movie.attrs['href']
print('[网页链接]', urls)
获取评分与获取内容的方法一样。但是这样存在一个问题,它输出的结果将评分和评价数放在了一起,如“9.4 783221人评价”,而通常在做分析时,评分存在一个变量中,评价数存在另一个变量中。这就需要利用正则表达式进行简单地=的文本处理。调用 re.compile(r'\d+\.?\d*') 获取字符串中的数字,第一个数字为电影的评分,第二个数字是电影的评论数。
# 爬取评分和评论数
info = tag.find(attrs={"class": "star"}).get_text()
info = info.replace('\n', ' ')
info = info.lstrip()
print('[评分评论]', info)
mode = re.compile(r'\d+\.?\d*') # 正则表达式获取数字
# print(mode.findall(info))
i = 0
for n in mode.findall(info):
if i == 0:
print('[分数]', n)
elif i == 1:
print('[评论]', n)
i = i + 1
3 链接跳转分析及详情页面爬取
前面爬取了电影的超链接地址,以《申肖克的救赎》为例,打开得到的超链接可以看到该电影的详细信息,如下图所示:
同样,首先给出完整代码:
import requests
from bs4 import BeautifulSoup
# 爬取详细信息
def getInfo(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
html = requests.get(url, headers=headers).text
# lxml:html解析库(把HTML代码转化成Python对象)
soup = BeautifulSoup(html, 'lxml')
# 电影简介
print('电影简介:')
info = soup.find(attrs={'id': 'info'})
print(info.get_text())
other = soup.find(attrs={"class": "related-info"}).get_text()
print(other.replace('\n', '').replace('', '')) # 过滤空格和换行
# 评论
print('评论信息:')
for tag in soup.find_all(attrs={"id": "hot-comments"}):
for comment in tag.find_all(attrs={"class": "comment-item"}):
com = comment.find("p").get_text() # 爬取段落 p
print(com.replace('\n', '').replace('', ''))
# 爬虫函数
def crawl(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
html = requests.get(url, headers=headers).text
# lxml:html解析库(把HTML代码转化成Python对象)
soup = BeautifulSoup(html, 'lxml')
for tag in soup.find_all(attrs={"class": "item"}):
# 爬取序号
num = tag.find('em').get_text()
print(num)
# 电影名称
name = tag.find_all(attrs={"class": "title"})
zwname = name[0].get_text()
print('[中文名称]', zwname)
# 网页链接
url_movie = tag.find(attrs={"class": "hd"}).a
urls = url_movie.attrs['href']
print('[网页链接]', urls)
getInfo(urls)
# 主函数
if __name__=='__main__':
i = 0
while i < 1:
print('页码:', i+1)
num = i * 25 # 每次显示 25 部,URL 序号按 25 增加
urls = 'https://movie.douban.com/top250?start=' + str(num) + '&filter='
crawl(urls)
i = i + 1
这里截取排名第四的电影《这个杀手不太冷》的输出结果进行展示:
3.1 爬取详情页面基本信息
首先对详情页面进行 DOM 树节点分析,其基本信息位于 <div class="artical">……</div> 标签下,核心内容位于该节点下的子节点中,即 <div id="info">……</div>。
info = soup.find(attrs={"id": "info"})
print(info.get_text)
3.2 爬取详情页面电影简介
同样,通过浏览器审查元素可以得到如下图所示的电影简介 HTML 源码,其电影简介位于 <div class="related-info">……</div> 节点下,他包括简短版(Short)简介和隐藏的详细版简介(all_hidden),这里通过 fin(attrs={"class":"related-info"}).get_text() 获取,代码如下:
other = soup.find(attrs={"class": "related-info"}).get_text()
print(other.replace('\n', '')).replace('', '') # 过滤空格和换行
3.3 爬取详情页面热门影评信息
热门影评信息位于“<div id="hot-comments">……</div>” 节点下,然后获取多个 class 属性为 “comment-item” 的 div 布局。当时用 find() 或 find_all() 函数进行爬取时,需要注意标签属性是 class 还是 id,或是其他,必须与之对应一致才能能正确爬取。
# 评论
print('评论信息:')
for tag in soup.find_all(attrs={"id": "hot-comments"}):
for comment in tag.find_all(attrs={"class": "comment-item"}):
com = comment.find("p").get_text() # 爬取段落 p
print(com.replace('\n', '').replace('', ''))
4 本文小结
至此,使用 BeautifulSoup 技术分析爬取豆瓣电影前 250 名电影信息的实例已经讲解完毕了,但在实际爬取过程中可能会由于某些页面不存在而导致爬虫停止,这时需要使用异常语句 " try - except - finally " 进行处理。同时,爬取过程中需要结合自己所需数据进行定位节点,存储至本地文件中,也需要结合字符串处理过滤一些多余的空格或换行。
在学习网络爬虫之前,首先要掌握分析网页节点、审查元素定位标签,甚至是翻页跳转、URL 分析等知识,然后才是通过 Python、Java 或 C# 实现爬虫的代码。
本文深入讲解了 BeautifulSoup 技术网页分析并爬取了豆瓣电影信息,同时,将所有爬取内容存储至 .txt 文件中。当然也可以存储至 Excel 、CSV、Json 文件中,甚至存储至数据库中,这将为后面的数据分析提供强大的数据支撑,使数据处理起来更加方便。
欢迎留言,一起学习交流~
感谢阅读