第一个Python爬虫

前言

网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。

如今Python这门语言越来越火,在外面看到的培训机构都有“Python爬虫”、“Python人工智能”这种招牌。而我们学校没有开设Python这一门课程,因此我学习Python仅仅是个人兴趣,也算是为以后的工作做一点点的准备吧。由于在此之前我都是用C/C++来做题目的,所以我对于Python的类与对象方面都不是很了解,我大概看了一下文档,有各种各样的由官方封装起来的方法供我们使用,还有很多第三方模块,总的来说对Python的第一印象是语法简单,涉及内容多。同时Python也是我学习的第一门面向对象的语言。希望自己能够好好体会对象的概念。

准备

在我们使用各种开发工具的时候,一般会遇到开发环境配置的问题。先说Dev-cppVisual Studio 201X这种IDE,它们其实是包括了代码编辑器、编译器、调试器和图形用户界面等工具,所以这种工具的使用也很简单,只需要照常安装就行,然后写一个"Hello World程序就成功了。

其实C/C++的编译器是GCC/G++,而上面所说的工具最核心的功能是调试阶段,可以实现语法高亮,自动提醒语法错误,设置断点等等功能。而编译的阶段其实是调用它自带的GCC/G++程序,于是一直到这里也没有涉及到开发环境的配置,也许很多人一直用Dev-cpp到大学毕业也不会配置一个C语言环境。

还是略过这一块比较好,只需要知道我们用Visual Studio Code来写一个C/C++程序是需要自己配置开发环境的。

在没有配置Python开发环境的前提下我们用记事本直接写一个print("Hello World")是无法执行的。其实Python的环境配置很简单(配置开发环境本就是一件很容易的事情),我们直接去官网下载Windows的可执行程序就行了,在安装阶段就有添加Python到系统的PATH这一个选项,我们安装好之后就可以开心的使用Python了。打开cmd输入python就可以看到我们的python版本信息:

1
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 21:26:53) [MSC v.1916 32 bit (Intel)] on win32

正文

写一个Python爬虫不是一件很难的事情。短短几十行代码就可以做到一个抓取某网站所有图片的功能,相比用C/C++刷题来说这个显得更有趣一些。于是打开了Visual Studio Code新建了一个文件test.py

首先我们要知道python爬虫是一个什么原理。简要的说便是一段程序自动打开一个网站,然后在网站上寻找自己需要的信息,最后把这些获取到的信息保存起来,因此我们知道了大概的步骤,一个简单的爬虫就只需要三步就可以完成爬取工作。

打开网页

在官方文档中我们可以找到一个叫做urllib的模块,利用这个模块我们就可以写出一个简单的爬虫程序。但是,我们可以用到一个更加方便的Python的第三方模块requests

requests 是⽤Python语⾔编写,基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库。它⽐ urllib 更加⽅便,可以节约我们⼤量的⼯作,完全满⾜HTTP测试需求。

相对而言人类总是喜欢简洁的,对比一下两者打开一个网页的语句就可以发现requests简洁优势。

1
2
response1 = requests.get(url = Weburl, headers = WebHead)
response2 = urllib.request.urlopen(url = WebUrl, headers = WebHead)

下面具体说一下如何使用requests.get()方法打开一个网站(以抓取百度贴吧精品图片为例)

1
2
3
4
5
6
7
8
9
import requests

WebUrl = "https://tieba.baidu.com/f?kw=%E5%A3%81%E7%BA%B8&ie=utf-8&tab=good"
WebHead = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Referer':'https://tieba.baidu.com'
}

response = requests.get(url = WebUrl, headers = WebHead)

在第一行我们需要导入requests模块,然后定义一下WbeUrl也就是我们需要爬虫去的网址。
然后需要了解一点是我们打开一个网站时会有相关信息集合在数据传输的头部Headers传给云端服务器校验。所以我们定义了一个WebHead来应对各种情况。有什么情况呢?就比如某些网站不允许爬虫进入,我们要做的就是修改上传的头部信息。

requests.get()方法很轻松的做到了这一点,只需要在方法内部加入参数headers即可。
User-Agent作用是服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。
Referer作用呢是告诉服务器我是从哪个页面链接过来的,服务器基此可以获得一些信息用于处理。

到这里运行这一段代码是没有什么问题的。那么我们运行之后得到了什么呢?
可以通过print(response.text)来查看,可以看到许多许多排版乱糟糟的HTML5/CSS3/JAVASCRIPT语句,也就是说text信息也就是网站的源码。

处理信息

requests.get()方法打开网站之后总不能不干事情,这里我选择用BeautifulSoup库来做下一步操作。本来一般来说写一个爬虫是需要用到正则表达式的,但是我太懒了太笨了,我选择BeautifulSoup

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库,它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。Beautiful Soup会帮你节省数小时甚至数天的工作时间。

BeautifulSoup文档里面很清楚的讲了安装以及使用,所以我们开开心心的用就得了。

1
2
3
4
5
6
7
8
9
10
11
import requests
from bs4 import BeautifulSoup

WebUrl = "https://tieba.baidu.com/f?kw=%E5%A3%81%E7%BA%B8&ie=utf-8&tab=good"
WebHead = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Referer':'https://tieba.baidu.com'
}

response = requests.get(url = WebUrl, headers = WebHead)
soup = BeautifulSoup(response.text, 'html.parser')

通过bs4模块导入了BeautifulSoup,然后用soup变量存储BeautifulSouphtml.parser方法处理response.text之后得到的信息。这就是处理信息的过程,一行代码就可以了。

获取信息

我们手动打开网址百度贴吧精品贴纸可以看到的是很多很多的帖子,事实上我们的爬虫在上一步也就进行到了这一步,很明显,我们是需要抓取图片而不是在这个界面停留,而图片在每一个帖子里面,那么我们可以点击第一个帖子进入即可,但是爬虫不会自动点击进入,所以我们需要处理信息得到每一个帖子的URL

这里我们需要审查网页元素,也就是检查网页的源代码。找帖子的URL不就是找对应的a标签嘛?

1
2
3
4
5
<ul id="thread_list" class="threadlist_bright j_threadlist_bright">
···
···
···
</ul>

经过审查元素我们可以发现所有帖子都在这个列表里面,很显然我们要做的就是用find()方法找到这一个ul

1
2
3
4
5
6
7
8
9
10
11
12
import requests
from bs4 import BeautifulSoup

WebUrl = "https://tieba.baidu.com/f?kw=%E5%A3%81%E7%BA%B8&ie=utf-8&tab=good"
WebHead = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Referer':'https://tieba.baidu.com'
}

response = requests.get(url = WebUrl, headers = WebHead)
soup = BeautifulSoup(response.text, 'html.parser')
a_list = soup.find('ul', id='thread_list').find_all('a', class_='j_th_tit')

这里我们又添加了一个语句。前面得到的soup变量是经过处理的BeautifulSoup对象呀,所有我们可以用find()方法来获取信息了。用BeautifulSoup就省去了写正则表达式的麻烦,可以看到find()方法里面是两个参数'ul'id='thread_list'。也就是从text中找到一个id是thread_list的列表,但是找到了列表还没完。

我们需要的是帖子的链接而不是这个列表,我们用浏览器继续审查元素。

1
2
3
<div class="threadlist_title pull_left j_th_tit "> 
<a rel="noreferrer" href="/p/4803144798" title="【壁纸】高清大图✨" target="_blank" class="j_th_tit ">【壁纸】高清大图✨</a>
</div>

可以发现,这个div里面的a标签不就是我们需要的嘛?对比其他帖子的链接,可以发现这些a标签都有同一个class,因此我们把这些a标签全部保存起来不就好了嘛?于是就有了find_all()方法,和前面的find()方法同理,加入对应的参数就可以了。当然,我们只是找到了而已,还需要保存起来,那么就用一个列表接受就好了。
print(a_list)(错误用法,列表的输出不是这样子写)可以得到如下信息:

1
2
3
4
5
6
<a class="j_th_tit" href="/p/4803144798" rel="noreferrer" target="_blank" title="【壁纸】高清大图✨">【壁纸】高清大图✨</a>
···
<a class="j_th_tit" href="/p/4645322258" rel="noreferrer" target="_blank" title="【壁纸】分享壁纸💗">【壁纸】分享壁纸💗</a>
···
<a class="j_th_tit" href="/p/4079735446" rel="noreferrer" target="_blank" title="【壁纸】少女の心">【壁纸】少女の心</a>
<a class="j_th_tit" href="/p/4198993236" rel="noreferrer" target="_blank" title="【壁纸】天晴">【壁纸】天晴</a>

可以看到a_list这个列表存储的是这些信息,但是还没完,我要a标签干嘛,不就是为了得到帖子的链接嘛?我只需要href的信息。

1
2
3
4
5
6
7
8
post_url_list = []
for a in a_list:
a['href'] = "https://tieba.baidu.com" + a['href']
post_url_list.append(a['href'])

#输出帖子的链接检查是否正确无误
for url in post_url_list:
print(url)

a标签里面的href里面是不全完整的链接,所以补全了再插入到post_url_list里面。

可以看输出信息:

1
2
3
4
5
6
7
8
9
10
https://tieba.baidu.com/p/4847606272
https://tieba.baidu.com/p/4645322258
https://tieba.baidu.com/p/4492902168
https://tieba.baidu.com/p/3446797781
https://tieba.baidu.com/p/4378860308
https://tieba.baidu.com/p/4364768066
https://tieba.baidu.com/p/4328735082
https://tieba.baidu.com/p/4357276836
https://tieba.baidu.com/p/4079735446
···

于是我们得到了当前页面所有帖子的链接,接下来就是进入每一个帖子(跌倒实现),到目前为止的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
from bs4 import BeautifulSoup

WebUrl = "https://tieba.baidu.com/f?kw=%E5%A3%81%E7%BA%B8&ie=utf-8&tab=good"

WebHead = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Referer': 'https://tieba.baidu.com'
}

response = requests.get(url = WebUrl, headers = WebHead)
soup = BeautifulSoup(response.text, 'html.parser')
a_list = soup.find('ul', id='thread_list').find_all('a', class_='j_th_tit')

post_url_list = []
for a in a_list:
a['href'] = "https://tieba.baidu.com" + a['href']
post_url_list.append(a['href'])

抓取目标

有了帖子的链接就好办了,我们只要进入每一个帖子,然后在里面寻找图片最后保存就好了呀。
有这么多链接在一个列表里面了,我们首先是需要迭代每一个帖子链接。我们人工操作,手动点入了一个帖子。看了看有很多人回帖,留住也放了很多图片,水贴的不少,酸酸怪也有,这里有各种各样的信息,我们需要的只是图片,我们审查元素。

1
<img class="BDE_Image" src="https://imgsa.baidu.com/forum/w%3D580/sign=7a1e5400e8fe9925cb0c695804a95ee4/c9034c086e061d954a4c7bfd73f40ad163d9caeb.jpg" size="62546" width="506" height="900">

右键图片审查元素就可以看到这个img标签。我们最后不就是需要这个src里面的链接吗?
刚才是人工做的,现在换成爬虫。爬虫进入第一个帖子的样子不是像极了爬虫第一次进入主页面的时候?所以操作很简答,和上面一样用requests.get方法打开帖子链接,然后用BeautifulSoup处理信息,然后用find()方法筛选信息…代码就是这样子了

1
2
3
4
5
6
7
8
for PostUrl in post_url_list:
response_post = requests.get(url = PostUrl, headers = WebHead)
soup_post = BeautifulSoup(response_post.text, 'html.parser')
img_url_list = soup_post.find_all('img', class_ = 'BDE_Image')

#输出图片的链接
for ImgUrl in img_url_list:
print(ImgUrl['src'])
1
2
3
4
5
6
···
https://imgsa.baidu.com/forum/w%3D580/sign=6b419e4e8d82b9013dadc33b438ca97e/e537e5dde71190efd27b27b6c91b9d16fcfa60dd.jpg
https://imgsa.baidu.com/forum/w%3D580/sign=9aef0b8db41c8701d6b6b2ee177e9e6e/4df7e0fe9925bc31e8fcb4f159df8db1cb13703c.jpg
https://imgsa.baidu.com/forum/w%3D580/sign=10361301d609b3deebbfe460fcbe6cd3/ea11632762d0f703d7f721b50ffa513d2697c510.jpg
https://imgsa.baidu.com/forum/w%3D580/sign=80ee8d6a8044ebf86d716437e9f8d736/b43d8794a4c27d1e39958fad1cd5ad6eddc4382e.jpg
···

好了,我们得到了终极目标,接下来只需要保存图片就行了。后面也就是迭代了,保存和切换路径都有固定的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import requests, os
from bs4 import BeautifulSoup

WebUrl = "https://tieba.baidu.com/f?kw=%E5%A3%81%E7%BA%B8&ie=utf-8&tab=good"

WebHead = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Referer': 'https://tieba.baidu.com'
}

response = requests.get(url = WebUrl, headers = WebHead)
soup = BeautifulSoup(response.text, 'html.parser')
a_list = soup.find('ul', id='thread_list').find_all('a', class_='j_th_tit')

post_url_list = []
for a in a_list:
if a['href'] not in post_url_list:
a['href'] = "https://tieba.baidu.com" + a['href']
post_url_list.append(a['href'])

path = 'E:/贴吧精品壁纸/'
os.chdir(path)
j = 1
for PostUrl in post_url_list:
response_post = requests.get(url = PostUrl, headers = WebHead)
soup_post = BeautifulSoup(response_post.text, 'html.parser')
img_url_list = soup_post.find_all('img', class_ = 'BDE_Image')

ImgUrl_list = []
for url in img_url_list:
ImgUrl_list.append(url['src'])

i = 1
for ImgUrl in ImgUrl_list:
print("正在保存: " + ImgUrl)
img_save = requests.get(ImgUrl, headers = WebHead)
with open("第" + str(i) + "个帖子" + "第" + str(j) + "张图片" +'.jpg', 'wb') as f:
f.write(img_save.content)
f.close()
i += 1
j += 1

自此,所有代码都写完了。运行以上代码的前提是需要安装好第三方模块以及E盘有一个文件夹“贴吧精品壁纸”。一个很简单的爬虫就这样子完成了,它会爬WebUrl界面所有帖子里面的所有图片资源,当然,速度并不是很快,完全可以多加优化。运行完不出意外应该有一千多张图片,中途理论上是不会中断的。具体的效果如图:
test

总结

感觉很有趣,看着自己写的代码成功的把图片一张张扒下来感觉很有成就感。虽然只是一个很简单的项目,还是让我感觉对python更加理解了一些,相比于阅读枯燥的官方文档,我更喜欢参考别人的项目代码来学习各种模块方法的使用。python大概就告一段落了,爬虫这块会继续学习下去,但是系统的学习python恐怕是没有必要了,我平时刷题都是用C/C++,python给我的帮助真的不是很大,只是玩一玩还好啦。人生苦短,我用python!加油。