大家好,我是赛博红兔。上一期我们聊了网页爬虫,它能自动帮我们上网、浏览网页、把有用的信息搬回来。我们学习了如何用Python的Requests库进行网页请求、图片下载、数据提交还有身份验证。那么等我们得到了网页响应之后,就需要对网页进行解析来提取想要的内容。为了展示这个过程,我们先来看看我们最终要实现的目标。今天,我们来试试爬取我的博客。(展示)大家看到在我主页上有很多不同的文章,每一篇文章都有一个标题,一个配图,还有一段文字简介。我们编写了一个爬虫来抓取这些内容,然后把这些信息整理到一个CSV表格中,方便查看。
要达到这个目标就需要用到BeautifulSoup库。我们可以在命令窗口用pip安装即可:pip install beautifulsoup4。安装好之后,我们还需要一个HTML网页解析器。这里我不会深入去讲解析器的细节,但不同解析器在处理HTML时可能结果会不一样,尤其是当HTML存在格式错误的时候。如果你的HTML是完美格式的,那用什么解析器区别不大。BeautifulSoup官方建议使用lxml这个解析器,它解析速度快,也比较可靠,我们这期就用它。同样用pip安装它:pip install lxml。当然我们还要安装上期介绍的Requests库来发送网页请求。
首先,我来简单介绍一下HTML,没有这方面基础的小伙伴可以跟着我的介绍慢慢上手。其实你不需要对HTML非常熟悉也可以写网页爬虫,但了解一点肯定是有帮助的。HTML是构建网页的一种标记语言,而不是编程语言。它把所有网页信息都是包在标签(tag)里。一些常用的标签你看得多了就能记住它们的作用。我这边有一个叫sample的非常简单的HTML文件。页面上就几个内容:一个大标题 “测试网站”,下面是两个文章的链接,每篇文章有一个标题、一段简介。这里大家看到的是我用浏览器打开的样子,但网页背后的源代码看起来是右边这个样子。你可以看到这个HTML文件是用标签结构组织起来的,每个标签都由 <标签名> 开始,然后用 </标签名> 结束。比如我们想找文章标题和简介。这里有个<div>分块标签,它的class也就是类型是 “article”,说明这是一篇文章。这个类型class我们在BeautifulSoup里可以用它来定位内容。这个<div>里面有一个<h2>二级标题,在这个<h2>标签里面还有一个<a>标签,也就是一个链接。这个链接的文本是“文章一 标题”,也就是展示在页面上的标题。链接的href属性就是目标网址指向的是 “article1.html”,也就是点击后跳转的页面。在标题下面,我们有一个<p>标签,也就是段落,里面就是文章的简介。这整个结构会在第二篇文章那里重复一遍,只是内容换了。
<!DOCTYPE html>
<html class="no-js" lang="zh-CN">
<head>
<title>一个简单的测试网页</title>
<meta charset="utf-8">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<h1 id="site_title">测试网站</h1>
<hr>
<div class="article">
<h2><a href="article_1.html">文章一 标题</a></h2>
<p>这里是文章的简介1</p>
</div>
<hr>
<div class="article">
<h2><a href="article_2.html">文章二 标题</a></h2>
<p>这里是文章的简介2</p>
</div>
<hr>
<script src="js/vendor/modernizr-3.5.0.min.js"></script>
<script src="js/plugins.js"></script>
<script src="js/main.js"></script>
</body>
</html>
现在我们来试着用BeautifulSoup提取这个简单的HTML网页。我们先导入两个我们用到的库BeautifulSoup和Requests。首先,我们把这个HTML文件加载到 BeautifulSoup,生成一个“soup”的对象。有两种方式可以把HTML加进去,一个是用字符串,一会我们从真实网站抓取时会用到。另一种就是这样直接用Python文件读写来读取本地HTML文件。我会把Python文件读取写入处理教学放在下面,不了解的朋友可以去看看。这里的”lxml” 是我们刚刚说过的解析器。我们来打印一下,我们看到整个HTML打印出来,不过全是挤在左边,不太好看。我们可以用prettify()方法给它美化一下,这样可以清楚看到标签之间的层级结构。
from bs4 import BeautifulSoup
import requests
import csv
# 简单网页解析
with open('sample.html', encoding='utf-8') as f:
soup = BeautifulSoup(f, 'lxml')
print(soup.prettify())
那么要进行批量爬取,首先我们先来爬取一篇文章的内容。用soup.find()方法,它会找到第一个class类型叫”article” 的div标签。打印一下我们看到提取了整一个第一篇文章的内容。接下来,我们要从里面提取标题。因为标题是在<h2>标签里面的<a>标签里,所以可以这样写:article.h2.a.text。然后简介是在<p>标签里,所以article.p.text。打印之后就会看到这篇文章的标题和简介了。现在我们已经能爬取第一篇文章的信息,接下来就可以照样画葫芦来批量抓取所有文章了。只要在一个for循环里面用find_all()方法,通过标签找到每篇文章位置,然后把刚才标题和简介爬取放在循环里,打印出来就可以了。
# 爬取单个文章信息
article = soup.find('div', class_='article')
print(article.prettify())
headline = article.h2.a.text
print(headline)
summary = article.p.text
print(summary)
刚刚我们是用本地HTML文件练习,接下来我们就要来真的了。我的博客首页上有很多文章,上面有标题、封面图和简介。如果我们想用刚才学到的技术来抓取这些内容,该怎么做呢?一起来看啊,我们先用Requests发送一个网页请求来抓取我博客的HTML源码。不清楚Requests库的朋友可以先去看看我上期内容,我会把链接放在下面。然后,这个HTML源码会是一个字符串的形式,我们把这段字符串传给BeautifulSoup,看看是不是成功获取到网页内容了?输出看起来是正常的HTML源码,而且真实的网站要比简单的网页要复杂多了!现在我们可以开始解析这个页面。
# 批量爬取所有文章信息
for article in soup.find_all('div', class_='article'):
headline = article.h2.a.text
summary = article.p.text
print(headline)
print(summary)
print()
我们回过头再看来一下博客,用浏览器的“检查”功能来看一下源码。用右键点击了文章的标题,然后点击“检查”。发现它在一个列表标签 <li class=’wp-block-post’> 的标签里面。所以我们就用find方法从这个标签开始抓。哦要注意,这里参数class后面有一个下划线。我们可以清楚看到它包含了视频标题、配图还有文本简介。接下来,我们来找找标题标签,我们点进去。发现标题在<h2>标签下面。我们就写article.h2.text。这样,我们就得到标题了。再来看配图标签。我们看到这里有一个<figure>标签,里面嵌套着<img>标签,也就是图片的源信息。通常<img>标签的 src 属性就是图片的地址。,后面跟着是它的大小啊,链接什么的。所以我们用article.figure.img。<img>标签有一个src属性,这个属性的值就是图片的地址。我们可以通过[‘src’]单独提取出图片的链接地址。当然啦,我们还可以用上一期学的方法用Requests发送请求把图片下载下来保存在本地。我们来一起运行看一看。最后是简介,我们再往下点进去,简介是在另一个列表标签’div’ class_=’wp-block-post-excerpt’下的<p>段落标签里面。所以我们还是用find方法找这个段落标签然后再吓到第一个<p>标签里面。运行就能看到简介啦。
# 赛博红兔博客爬取(请手下留情)
url = 'https://cyberhongtu.com/'
source = requests.get(url).text
soup = BeautifulSoup(source, 'lxml')
print(soup.prettify())
article = soup.find('li', class_='wp-block-post')
print(article.prettify())
# 图片爬取
figure = article.figure.img
figure_url = figure['src']
response = requests.get(figure_url)
with open('cover.jpg', 'wb') as f:
f.write(response.content)
# 简介爬取
summary = article.find('div', class_='wp-block-post-excerpt').p.text
print(summary)
我们知道抓住一篇文章的结构,其实就是整个爬虫逻辑的缩影。下面就是遍历所有文章,爬取它们所有的内容。同样,我们用for循环和find_all方法依次定位文章,然后把标题、配图和简介的爬取统统放到里头。另外,为了图片在保存的时候命名不重复,我们又在循环里加一个不断递增的序号。最底下再加了一个空的print为了把每篇文章都空一行分开。我们运行一下来看看效果。怎么样不错吧?每张封面也都下载到本地了。
# 批量爬取
csv_file = open('blog_scrape.csv', 'w', newline='', encoding='utf-8-sig')
csv_writer = csv.writer(csv_file)
csv_writer.writerow(['标题', '简介', '图片链接'])
index = 1
for article in soup.find_all('li', class_='wp-block-post'):
headline = article.h2.text
print(headline)
figure = article.figure.img
figure_url = figure['src']
response = requests.get(figure_url)
with open(f'cover_{index}.jpg', 'wb') as f:
f.write(response.content)
print(figure_url)
summary = article.find('div', class_='wp-block-post-excerpt').p.text
print(summary)
print()
index += 1
csv_writer.writerow([headline, summary, figure_url])
csv_file.close()
最后,我们把这些数据保存到CSV文件里,方便查看。首先,我们需要导入 Python的csv库,然后在主程序顶部,也就是for循环开始之前,我们打开一个新的CSV文件,准备写入。为了写入数据,我们还需要创建一个CSV的写入器,然后写入表头也就是每一列的名称:’标题’,’简介’,和’图片链接’。在循环里,爬取完标题、简介和视频链接之后,就可以写入CSV文件了。最后,当循环结束后,我们要记得关闭CSV文件。运行程序后,会在当前目录下生成一个csv 文件。我们可以用Excel打开,这里就是我们爬取完的首页每篇文章的标题、简介和配图链接了。
好啦,BeautifulSoup这个库就先学到这里了!我还想补充几点建议:
- 要爬取大型网站可以先看看是不是有API。像Twitter、Facebook、YouTube这类大网站都是有公共API,国内网站也有但是放开的程度更低,申请流程更复杂,而且限制更多。用这些API获取数据通常更可靠、更高效,也不会违规违法。
- 在你爬取数据的时候要请对网站服务器友好。你的程序可以非常快速地向网站发送请求,但请注意不要恶意地去频繁访问他人的网站。否则对方可能会封你IP甚至报官啊。
- 麻烦各位看完这个教程之后,不要用爬虫一直疯狂访问我的主页,请大伙手下留情啊。
好了,本频道将同步在B站和油管上更新,如果你喜欢我的内容,请不要忘记点赞、订阅和分享,这样就不会错过我更新的内容,欢迎在评论区提出你的想法和建议。今天就到这里,再见吧!

Leave a comment