爬虫基础知识+豆瓣电影实战
什么是爬虫
简单来说,爬虫就是获取网页并提取和保存信息的自动化程序,爬虫能够自动请求网页,并将所需要的数据抓取下来。通过对抓取的数据进行处理,从而提取出有价值的信息进行存储使用。
为什么用Python做爬虫
首先您应该明确,不止 Python 这一种语言可以做爬虫,诸如 Java、C/C++、PHP 都可以用来写爬虫程序,但是相比较而言 Python 做爬虫是最简单的。下面对它们的优劣势做简单对比:
Java 也经常用来写爬虫程序,但是 Java 语言本身很笨重,代码量很大,因此它对于初学者而言,入门的门槛较高;C/C++ 运行效率虽然很高,但是学习和开发成本高。写一个小型的爬虫程序就可能花费很长的时间,PHP:对多线程、异步支持不是很好,并发处理能力较弱。
而 Python 语言,其语法优美、代码简洁、开发效率高、支持多个爬虫模块,比如 urllib、requests、Bs4、lxml 等。Python 的请求模块和解析模块丰富成熟,并且还提供了强大的 Scrapy 框架,让编写爬虫程序变得更为简单。因此使用 Python 编写爬虫程序是个非常不错的选择。
编写爬虫注意事项
爬虫是一把双刃剑,它给我们带来便利的同时,也给网络安全带来了隐患。有些不法分子利用爬虫在网络上非法搜集网民信息,或者利用爬虫恶意攻击他人网站,从而导致网站瘫痪的严重后果。关于爬虫的如何合法使用,推荐阅读《中华人民共和国网络安全法》。
因此大家在使用爬虫的时候,要自觉遵守国家法律法规,不要非法获取他人信息,或者做一些危害他人网站的事情。
编写爬虫的流程
爬虫程序与其他程序不同,它的的思维逻辑一般都是相似的,所以无需我们在逻辑方面花费大量的时间。下面对 Python 编写爬虫程序的流程做简单地说明:
先由 requests 模块的 get() 方法打开 URL 得到网页 HTML 对象。
使用浏览器打开网页源代码分析网页结构以及元素节点。
通过解析库(lxml、BeautifulSoup)或则正则表达式提取数据。
存储数据到本地磁盘或数据库。
当然也不局限于上述一种流程。编写爬虫程序,需要您具备较好的 Python 编程功底,这样在编写的过程中您才会得心应手。爬虫程序需要尽量伪装成人访问网站的样子,而非机器访问,否则就会被网站的反爬策略限制,甚至直接封杀 IP,相关知识会在后续内容介绍。
爬虫程序之所以可以抓取数据,是因为爬虫能够对网页进行分析,并在网页中提取出想要的数据。在学习 Python 爬虫模块前,我们有必要先熟悉网页的基本结构,这是编写爬虫程序的必备知识。
如果您熟悉前端语言,那么您可以轻松地掌握本节知识。
网页一般由三部分组成,分别是 HTML(超文本标记语言)、CSS(层叠样式表)和 JavaScript(简称“JS”动态脚本语言),它们三者在网页中分别承担着不同的任务。
HTML 负责定义网页的内容
CSS 负责描述网页的布局
JavaScript 负责网页的行为
接下来我们来一起简单了解一下网页的基础知识。
网页基础知识
网页结构
HTML
HTML 是网页的基本结构,它相当于人体的骨骼结构。网页中同时带有“<”、“>”符号的都属于 HTML标签。常见的 HTML 标签如下所示:
<!DOCTYPE html> 声明为 HTML5 文档
<html>..</html> 是网页的根元素
<head>..</head> 元素包含了文档的元(meta)数据,如 <meta charset="utf-8"> 定义网页编码格式
为 utf-8。
<title>..<title> 元素描述了文档的标题
<body>..</body> 表示用户可见的内容
<div>..</div> 表示框架
<p>..</p> 表示段落
<ul>..</ul> 定义无序列表
<ol>..</ol>定义有序列表
<li>..</li>表示列表项
<img src="" alt="">表示图片
<h1>..</h1>表示标题
<a href="">..</a>表示超链接
编写如下代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>跟拉拉学编程</title></head>
<body><a href="www.youbafu.com">点击访问</a>
<h1>跟拉拉学Python</h1>
<h2>Python爬虫</h2>
<div><p>认识网页结构</p>
<ul>
<li>HTML</li>
<li>CSS</li>
</ul>
</div>
</body>
</html>
运行结果如下图所示:
CSS
CSS 表示层叠样式表,其编写方法有三种,分别是行内样式、内嵌样式和外联样式。CSS 代码演示如 下:
<!DOCTYPE html>
<html>
<head>
<!-- 内嵌样式 -->
<style type="text/css"> body{ background-color:yellow; }
p{ font-size: 30px; color: springgreen; } </style>
<meta charset="utf-8">
<title>跟拉拉学编程</title>
</head>
<body>
<!-- h1标签使用了行内样式 -->
<h1 style="color: blue;">跟拉拉学Python</h1>
<a href="www.youbafu.com">点击访问</a>
<h2>Python爬虫</h2>
<div>
<p>认识网页结构</p>
<ul>
<li>HTML</li>
<li>CSS</li>
</ul>
</div>
</body>
</html>
运行结果如下图所示:
如上图所示内嵌样式通过 style 标签书写样式表:
<style type="text/css"></style>
而行内样式则通过 HTML 元素的 style 属性来书写 CSS 代码。注意,每一个 HTML 元素,都有 style,class,id,name,title 属性。
外联样式表指的是将 CSS 代码单独保存为以 .css 结尾的文件,并使用 引入到所需页面:
<head>
<link rel="stylesheet" type="text/css" href="mystyle.css">
</head>
当样式需要被应用到多个页面的时候,使用外联样式表是最佳的选择。
JavaScript
JavaScript 负责描述网页的行为,比如,交互的内容和各种特效都可以使用 JavaScript 来实现。当然 可以通过其他方式实现,比如 jQuery、还有一些前端框架( vue、React 等),不过它们都是在“JS”的基础上 实现的。
简单示例:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
body{ background-color: rgb(220, 226, 226); }
</style>
<meta charset="utf-8">
<title>
跟拉拉学编程
</title>
</head>
<body>
<h1 style="color: blue;">
跟拉拉学Python
</h1>
<h2>
Python爬虫
</h2>
<p>
点击下方按钮获取当前时间
</p>
<button onclick="DisplayDate()">
点击这里
</button>
<p id="time" style="color: red;">
</p>
<!-- script标签内部编写js代码 -->
<script>
function DisplayDate() {
document.getElementById("time").innerHTML = Date()
}
</script>
</div>
</body>
</html>
运行结果如下:
如果用人体来比喻网站结构的话,那么 HTML 是人体的骨架,它定义了人的嘴巴、眼睛、耳朵长在什么位置;CSS 描述了人体的外观细节,比如嘴巴长什么样子,眼睛是双眼皮还是单眼,皮肤是黑色的还是 白色的等;而 JavaScript 则表示人拥有的技能,例如唱歌、打球、游泳等。
当我们在编写一个爬虫程序前,首先要明确待爬取的页面是静态的,还是动态的,只有确定了页面类 型,才方便后续对网页进行分析和程序编写。对于不同的网页类型,编写爬虫程序时所使用的方法也不尽 相同。
网页形态
静态网页
静态网页是标准的 HTML 文件,通过 GET 请求方法可以直接获取,文件的扩展名是.html .htm 等,网面中可以包含文本、图像、声音、FLASH 动画、客户端脚本和其他插件程序等。静态网页是网站建设的基础,早期的网站一般都是由静态网页制作的。静态并非静止不动,它也包含一些动画效果,这一点不要误解。
我们知道,当网站信息量较大的时,网页的生成速度会降低,由于静态网页的内容相对固定,且不需要连接后台数据库,因此响应速度非常快。但静态网页更新比较麻烦,每次更新都需要重新加载整个网页。
静态网页的数据全部包含在 HTML 中,因此爬虫程序可以直接在 HTML 中提取数据。通过分析静态网页的 URL,并找到 URL查询参数的变化规律,就可以实现页面抓取。与动态网页相比,并且静态网页对搜索引擎更加友好,有利于搜索引擎收录。
动态网页
动态网页指的是采用了动态网页技术的页面,比如 AJAX(是指一种创建交互式、快速动态网页应用的网页开发技术)、ASP(是一种创建动态交互式网页并建立强大的 web 应用程序)、JSP(是 Java 语言创建动 态网页的技术标准) 等技术,它不需要重新加载整个页面内容,就可以实现网页的局部更新。动态页面使用“动态页面技术”与服务器进行少量的数据交换,从而实现了网页的异步加载。
抓取动态网页的过程较为复杂,可以使用前面学习的网页自动化来模拟操作实现数据的爬取。
审查网页元素
对于一个优秀的爬虫工程师而言,要善于发现网页元素的规律,并且能从中提炼出有效的信息。因此,在动手编写爬虫程序前,必须要对网页元素进行审查。本节将讲解如何使用“浏览器”审查网页元素。
浏览器都自带检查元素的功能,不同的浏览器对该功能的叫法不同, 谷歌(Chrome)浏览器称为“检 查”,而 Firefox 则称“查看元素”,尽管如此,但它们的功却是相同的,本教程推荐使用谷歌(Chrome)浏览 器。
检查百度首页
下面以检查百度首页为例:首先使用 Chrome 浏览器打开百度,然后在百度首页的空白处点击鼠标右 键(或者按快捷键:F12),在出现的会话框中点击“检查”,并进行如图所示操作:点击审查元素按钮,然 后将鼠移动至a您想检查的位置,比如百度的输入框,然后单击,此时就会将该位置的代码段显示出来, 如下图所示:
最后在该代码段处点击右键,在出现的会话框中选择 “复制” 选项卡,并在二级会话框内选择 “复制元 素”,如下所示:
<input id="kw" name="wd" class="s ipt" value="" maxlength="255" autocomplete="off">
依照上述方法,您可以检查页面内的所有元素。
检查网页结构
对于爬虫而言,检查网页结构是最为关键的一步,需要对网页进行分析,并找出信息元素的相似性。 下面以 豆瓣电影 Top250 豆瓣电影 Top 250 为例,检查每部影片的 HTML 元素结构。 如下图所示:
第一部影片的html代码段如下所示:
<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://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp"
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>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗
宾斯 Tim Robbins /...<br>
1994 / 美国 / 犯罪 剧情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>2564578人评价</span>
</div>
<p class="quote">
<span class="inq">希望让人自由。</span>
</p>
</div>
</div>
</div>
</li>
接下来检查第二部影片的html代码,如下所示
<li>
<div class="item">
<div class="pic">
<em class="">2</em>
<a href="https://movie.douban.com/subject/1291546/">
<img width="100" alt="霸王别姬" src="https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2561716440.webp" class="">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1291546/" class="">
<span class="title">霸王别姬</span>
<span class="other"> / 再见,我的妾 / Farewell My Concubine</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 陈凯歌 Kaige Chen 主演: 张国荣 Leslie Cheung / 张丰毅 Fengyi Zha...<br>
1993 / 中国大陆 中国香港 / 剧情 爱情 同性
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.6</span>
<span property="v:best" content="10.0"></span>
<span>2260298人评价</span>
</div>
<p class="quote">
<span class="inq">风华绝代。</span>
</p>
</div>
</div>
</div>
</li>
经过对比发现,除了每部影片的信息不同之外,它们的 HTML 结构是相同的,比如每部影片都使用<div class="item"></div> 标签包裹起来。这里我们只检查了两部影片,如果在实际编写时,同学们可 以多检查几部,从而确定它们的 HTML 结构是相同的。
提示:通过检查网页结构,然后发现规律,这是编写爬虫程序最为重要的一步。
豆瓣电影实战
以豆瓣电影Top250为例,我们使用上节课学过的正则表达式,结合分析网页元素来爬取电影的简单信 息并存储到Excel中,或者存储为json文件。
同学们再来一起看看每部影片的html代码元素结构:
<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.webp" 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>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994 / 美国 / 犯罪 剧情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>3060501人评价</span>
</div>
<p class="quote">
<span class="inq">希望让人自由。</span>
</p>
</div>
</div>
</div>
</li>
这里假设我们要爬取影片的:电影名、电影别名、导演和演员信息、发行时间、地区、类别、推荐 度、评分、评论数、简介信息,我们可以编写对应的正则表达式,将每部影片的需要信息匹配出来,正则表达式如下所示:
# 正则表达式 匹配规则
pattern = re.compile('<li>.*?"title">(.*?)<' +
'.*?"title">(.*?)<.*?"other">(.*?)<' +'.*?"bd".*?p class="">(.*?)</p>' + # 注意class后面有=""
'.*?"star".*?rating(.*?)-t' +
'.*?"v:average">(.*?)<' +
'.*?<span>(\d+)人' +
代码中用到请求模块 requests 和 pandas 库,需要先安装,在终端(Terminal)里输入:
pip install requests
pip install pandas
import json
import os.path
import pandas as pandas
import requests
import re
import csv
# 设置请求头: 模拟浏览器
headers = {
'Host': 'movie.douban.com',
'Origin': 'movie.douban.com',
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Mobile Safari/537.36',
}
# 正则表达式 匹配规则
pattern = re.compile('<li>.*?"title">(.*?)<' +
'.*?"title">(.*?)<.*?"other">(.*?)<' +
'.*?"bd".*?p class="">(.*?)</p>' + # 注意class后面有=""
'.*?"star".*?rating(.*?)-t' +
'.*?"v:average">(.*?)<' +
'.*?<span>(\d+)人' +
'.*?inq">(.*?)<.*?</li>', re.S)
def get_films(pages=5):
films = []
for i in range(pages):
url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)
text = requests.get(url, headers=headers).text
for item in re.findall(pattern, text):
print(item)
m_info = item[3].replace(' ', '').strip().split('<br>\n')
film = {
'电影名': item[0],
'别名': item[1].replace(' ', '').replace(' ', '').strip() +
item[2].replace(' ', '').replace(' ', '').strip(),
'导演和演员': m_info[0],
'发行时间': m_info[1].split('/')[0].strip(),
'地区': m_info[1].split('/')[1].strip(),
'类别': m_info[1].split('/')[2].strip(),
'推荐度': '.'.join(list(item[4])),
'评分': item[5],
'评论数': item[6],
'简介': item[7]
}
films.append(film)
return films
def write_cvs(data):
with open(r'data/re_films.csv', 'w', encoding='utf-8') as f:
w = csv.DictWriter(f, fieldnames=data[0].keys())
w.writeheader()
w.writerows(data)
def write_excel(data):
pdfile = pandas.DataFrame(data)
pdfile.to_excel(r'data/re_films.xlsx', sheet_name="豆瓣电影")
def write_json(data):
s = json.dumps(data, indent=4, ensure_ascii=False)
with open(r'data/re_films.json', 'w', encoding='utf-8') as f:
f.write(s)
def start():
data = list(get_films(1))
if not os.path.exists('data'):
os.mkdir('data')
write_json(data)
# write_excel(data)
# write_cvs(data)
if __name__ == '__main__':
start()
'推荐度': '.'.join(list(item[4]))这段代码将 `item[4]` 的每个字符用 `'.'` 连接起来。具体步骤如下:
1. **`list(item[4])`**: 将 `item[4]` 转换为字符列表,其中每个字符都是列表的一个元素。
2. **`'.'.join(...)`**: 将字符列表中的每个字符用 `'.'` 连接起来,生成一个新的字符串。### 示例
假设 `item[4]` 是 `'hello'`,则:
1. **`list(item[4])`**: `['h', 'e', 'l', 'l', 'o']`
2. **`'.'.join(['h', 'e', 'l', 'l', 'o'])`**: `'h.e.l.l.o'`最终结果是字符之间用 `'.'` 连接的字符串。
m_info = item[3].replace(' ', '').strip().split('<br>\n')这段代码对
item[3]
进行了一系列字符串处理操作:
replace(' ', '')
: 替换字符串中的
为一个空字符串(即删除所有
)。strip()
: 去除字符串开头和结尾的空白字符(如空格、换行)。split('<br>\n')
: 按<br>\n
进行分割,返回一个列表,列表中的每个元素是按此分隔符分割出来的子串。这整个过程将
item[3]
中的 HTML 特殊字符和换行符处理后,拆分成一个子串列表。
pdfile.to_excel(r'data/re_films.xlsx', sheet_name="豆瓣电影")这段代码将 `pdfile` 数据框(DataFrame)保存为一个 Excel 文件。具体操作如下:
- **`to_excel(...)`**: 这是 Pandas 数据框的一个方法,用于将数据框写入 Excel 文件。
- **`r'data/re_films.xlsx'`**: 指定 Excel 文件的路径和文件名。`r` 表示原始字符串,避免转义字符问题。
- **`sheet_name="豆瓣电影"`**: 指定在 Excel 文件中保存数据的工作表名称为 "豆瓣电影"。### 示例
如果 `pdfile` 是一个数据框,这段代码会将其保存到名为 `re_films.xlsx` 的 Excel 文件中,并在该文件中创建一个名为 "豆瓣电影" 的工作表。
def write_cvs(data): with open(r'data/re_films.csv', 'w', encoding='utf-8') as f: w = csv.DictWriter(f, fieldnames=data[0].keys()) w.writeheader() w.writerows(data)
import csv
: 引入csv
模块,这个模块提供了用于读写 CSV 文件的工具。
def write_cvs(data):
: 定义一个名为write_cvs
的函数,接受一个参数data
,该参数应是一个包含字典的列表。
with open(r'data/re_films.csv', 'w', encoding='utf-8') as f:
:
open(r'data/re_films.csv', 'w', encoding='utf-8')
: 打开一个名为re_films.csv
的文件,如果文件不存在会创建它。'w'
模式表示写入模式,会清空文件中原有的内容。encoding='utf-8'
指定使用 UTF-8 编码。as f:
: 将打开的文件对象赋给变量f
,并在with
语句块结束后自动关闭文件。
w = csv.DictWriter(f, fieldnames=data[0].keys())
:
csv.DictWriter(f, fieldnames=data[0].keys())
: 创建一个DictWriter
对象,用于将字典数据写入 CSV 文件。fieldnames
参数指定 CSV 文件的列名,这里使用data[0].keys()
来获取第一个字典的键作为列名。
w.writeheader()
: 写入 CSV 文件的表头,表头由fieldnames
指定的列名组成。
w.writerows(data)
: 将data
中的所有字典写入 CSV 文件。data
应该是一个字典列表,每个字典表示一行数据。
课程总结
本节课正式进入了爬虫的内容,学习巩固了爬虫的一些基础知识,同时还学习了静态网页的相关基础 知识,毕竟爬虫的本质是对网页元素进行分析处理,只有对相关的基础知识牢固掌握了,写起爬虫来才会 更加轻松自如,最后结合正则表达式对豆瓣电影Top250进行了实战爬取。
课后习题
编程题
根据豆瓣电影实战的源码,利用openpyxl库编写write_excel2()函数,并将爬取到的前3页数据利用该函数保存到 data/re_films2.xlsx 中,保存格式如下图:
import openpyxl
from openpyxl import Workbook
import json
import os.path
import pandas as pandas
import requests
import re
import csv
# 设置请求头: 模拟浏览器
headers = {
'Host': 'movie.douban.com',
'Origin': 'movie.douban.com',
'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Mobile Safari/537.36',
}
# 正则表达式 匹配规则
pattern = re.compile('<li>.*?"title">(.*?)<' +
'.*?"title">(.*?)<.*?"other">(.*?)<' +
'.*?"bd".*?p class="">(.*?)</p>' + # 注意class后面有=""
'.*?"star".*?rating(.*?)-t' +
'.*?"v:average">(.*?)<' +
'.*?<span>(\d+)人' +
'.*?inq">(.*?)<.*?</li>', re.S)
def get_films(pages=3):
films = []
for i in range(pages):
url = 'https://movie.douban.com/top250?start={}&filter='.format(i * 25)
text = requests.get(url, headers=headers).text
for item in re.findall(pattern, text):
print(item)
m_info = item[3].replace(' ', '').strip().split('<br>\n')
film = {
'电影名': item[0],
'别名': item[1].replace(' ', '').replace(' ', '').strip() +
item[2].replace(' ', '').replace(' ', '').strip(),
'导演和演员': m_info[0],
'发行时间': m_info[1].split('/')[0].strip(),
'地区': m_info[1].split('/')[1].strip(),
'类别': m_info[1].split('/')[2].strip(),
'推荐度': '.'.join(list(item[4])),
'评分': item[5],
'评论数': item[6],
'简介': item[7]
}
films.append(film)
return films
def write_excel2(data):
# 创建一个工作簿
wb = Workbook()
ws = wb.active
ws.title = "豆瓣电影"
# 写入表头
columns = ['电影名', '别名', '导演和演员', '发行时间', '地区', '类别', '推荐度', '评分', '评论数', '简介']
ws.append(columns)
# 写入数据
for film in data:
ws.append([
film.get('电影名', ''),
film.get('别名', ''),
film.get('导演和演员', ''),
film.get('发行时间', ''),
film.get('地区', ''),
film.get('类别', ''),
film.get('推荐度', ''),
film.get('评分', ''),
film.get('评论数', ''),
film.get('简介', '')
])
# 保存到文件
wb.save('data/re_films2.xlsx')
def start():
data = list(get_films(3)) # 获取前3页数据
if not os.path.exists('data'):
os.mkdir('data')
write_excel2(data)
if __name__ == '__main__':
start()