第一课:文件爬取
1.环境准备
大家学爬虫之前,首先需要了解爬虫相关的法律法规,需要明确哪些内容是可以爬取的,哪些内容是不可以爬取的,我在这里只讲一个最简单的方法,每个网站的开发者都会在网站的根目录放上一个robots.txt,例如百度的就是https://www.baidu.com/robots.txt ,淘宝的就是https://uland.taobao.com/robots.txt ,这个文件里就清楚地说明了该网站哪些内容是可以爬取或者禁止爬取的。
本门课练习用的网站是我自己写的,大家直接下载就可以直接使用。
为了更好的模拟真实的网站,我们可以下载一个小皮面板,https://www.xp.cn/
安装好之后,在网站选项卡中启动apache服务器,然后选中网站选项卡,点击创建网站,这里的域名大家可以随便取一个,但是不建议用纯数字,因为我们的80端口已经被占用了,所以我就换一个5678端口好了,创建好之后,点击管理—打开根目录,将我们刚才下载的练习网站解压之后整体复制进去,最后点击管理—打开网站,就可以看到我们为大家准备的网站了,网站上有很多信息,包括一些图片、视频、数据以及文件,我们这节课就带大家爬取下方的txt文件。
2.思路梳理(爬虫基本步骤)
首先,我们需要先明确爬虫的基本步骤,然后我们再来带着大家一步步的去做,
- 第一步是找到资源的地址,这就好比我们如果要去取快递,我们得先知道快递站的地址。
- 第二步是发送网络请求,获取到资源对象,这就好比我们已经到了快递站,我们现在要向工作人员发起请求,告诉工作人员我们想要取快递,紧接着工作人员就会把快递给我们。
- 第三步是要将刚才获取的资源对象保存在本地,这就好比我们拿到快递之后要拿回家放着。
3.任务布置
爬取网页中“相关下载”的三个txt文件内容,并存放到本地
4.任务实战
1.找到资源地址
还记我们刚才所说的第一步吗?我们要使用浏览器定位元素找到图片的地址,我们也可以叫做url,意思是网络资源地址。
我们先按下F12打开浏览器的调试工具,然后使用左上角的元素定位工具,鼠标选中环保小知识1.txt,我们就可以看到它其实是一个a标签,a标签中的href属性就是资源的地址,这是一个相对路径,鼠标悬浮在上面可以看到完整的绝对地址,或者直接双击也可以。
最后我们将刚才获取到的地址通过赋值语句放到变量里就行了,变量名根据见名知义的原则,就叫url好了,另外注意这串地址属于字符串,别忘了在两边加上单引号。
# 第一步、找到资源地址
url = 'http://weike:5678/static/%E7%8E%AF%E4%BF%9D%E5%B0%8F%E7%9F%A5%E8%AF%861.txt'
2.发送请求,获取资源
在发起请求之前,我们首先要安装网络请求相关的第三方库,我这里只说一下windows系统是如何安装的,我们同时按下win+r键,输入cmd,按下回车,进入命令行,输入pip install requests安装。
安装好之后就可以导入requests库
接下来我们通过requests库中的get方法去发送一个get请求,这个方法只有一个必填项,就是请求资源的地址url,这也很好理解,就像我们之前说的,我们我们如果要去取快递,我们必须得知道快递站的地址,告诉对方我们想要取快递时,也就是发送请求,我们就会得到一个响应对象,我们通常叫做Response对象(取变量名的时候我一般简写为res),我们现在来看看这个Response对象身上有一些什么属性吧。
Response对象属性
属性 | 作用 |
---|---|
Response.status_code | 响应的状态码,用于检查请求是否成功 |
Response.content | 把Response对象转换为二进制数据 |
Response.text | 把Response对象转换为字符串数据 |
Response.encoding | Response对象的编码 |
常见响应状态码解释
响应状态码 | 说明 | 说明 |
---|---|---|
1xx | 请求收到 | 可以继续请求 |
2xx | 请求成功 | 虽然现在成功了,但是也有可能之后请求频率太高被限制,还是得小心 |
3xx | 重定向 | 可以尝试使用代理访问 |
4xx | 客户端错误 | 禁止访问 |
5xx | 服务器端错误 | 这个不关我们的事情,也解决不了,是服务器那边出了问题,所有人都访问不了 |
# 先引入第三方库
import requests
# 第一步、找到资源地址
url = 'http://weike:5678/static/%E7%8E%AF%E4%BF%9D%E5%B0%8F%E7%9F%A5%E8%AF%861.txt'
# 第二步、发送请求,获取响应对象
res = requests.get(url)
# 可以看到,res是一个Response对象
print(type(res))
# 接下来打印状态码,看看响应是否成功了
print(res.status_code)
# 发现乱码了,需要调整文本的编码,这里使用的是万国码,是国际编码,通用性最高,一般可以解决,编码相关知识这里不展开
res.encoding = 'utf-8'
# 接下来打印响应的文本内容
print(res.text)
3.文件保存
变量里的数据是临时的,一旦将编辑器关闭就什么也没有了,如果如果想将刚才爬取到的数据长期存放到我们的计算机上,还需要学习文件保存相关内容。
open函数
模式 | 作用 | 二进制模式 |
---|---|---|
r(read,读) | 只读,指针在文件开头,文件不存在则报错 | rb二进制读 |
w(write,写) | 只写,文件不存在则新建,存在则覆盖 | wb二进制写 |
a(append,追加) | 追加,文件存在指针放在末尾,文件不存在则新建 | ab二进制追加 |
# 先引入第三方库
import requests
# 第一步、找到资源地址
url = 'http://weike:5678/static/%E7%8E%AF%E4%BF%9D%E5%B0%8F%E7%9F%A5%E8%AF%861.txt'
# 第二步、发送请求,获取响应对象
res = requests.get(url)
# 可以看到,res是一个Response对象
print(type(res))
# 接下来打印状态码,看看响应是否成功了
print(res.status_code)
# 发现乱码了,需要调整文本的编码,这里使用的是万国码,是国际编码,通用性最高,一般可以解决,编码相关知识这里不展开
res.encoding = 'utf-8'
# 接下来打印响应的文本内容
print(res.text)
# 第三步、将数据保存在本地文件中
# 通过open函数打开一个txt文件,如果没有的话会自动创建的,不用我们提前建好文件
# 这里用了w写模式,因为我们打开文件是为了往里面写东西
# 之后就可以得到一个file对象,就是我们通过open函数创建的文件
file = open('./环保小知识.txt','w')
# 接下来使用file对象的write方法可以往文件里写入内容
file.write(res.text)
# 别忘了要关闭文件,否则会占用系统的资源
file.close()
5. 课后练习
写一个循环,一次性爬取所有的txt文件。
提示:三个txt文件名是有规律的,找找看是什么。
第二课:图片爬取
爬取图片和爬取文件的步骤是一样的,只有有细微的区别,我们现在还是按照以下三个步骤给大家讲解
1.任务布置
爬取本网站顶部的logo图片,并存放到本地
2.任务实战
1.找到资源地址
方法和之前一样,我们先按下F12打开浏览器的调试工具,然后使用左上角的元素定位工具,鼠标选中你想要爬取的图片,看到它其实是一个img标签,img标签中的src属性就是资源的地址,这是一个相对路径,鼠标悬浮在上面可以看到完整的绝对地址。
最后我们将刚才获取到的地址通过赋值语句放到变量里就行了
url = 'http://weike:5678/static/logo.jpg'
2.发送请求,获取资源
发送请求需要用到requests库,之前已经安装好了,现在直接导入。
同样是通过requests.get()方法发送请求,获得响应对象,但是如果打印这个响应对象的text(文本内容),我们发现全是乱码,因为我们爬取的是一张图片,而图片是用二进制进行编码的,所以我们这个时候应该打印图片对象的二进制内容,还记得图片对象的属性吗?再带大家复习一下。
Response对象属性
属性 | 作用 |
---|---|
Response.status_code | 响应的状态码,用于检查请求是否成功 |
Response.content | 把Response对象转换为二进制数据 |
Response.text | 把Response对象转换为字符串数据 |
Response.encoding | Response对象的编码 |
import requests
url = 'http://weike:5678/static/logo.jpg'
res = requests.get(url)
print(res.status_code) #打印对象的响应状态码,以检查请求是否成功
print(res.content) #打印对象的二进制编码内容
3.文件保存
我们知道,文字我们可以使用txt文件进行保存,而图片信息则应该保存在图片类型的文件中,图片类型的文件有哪些呢?常见的有jpg、png、gif等等,这些都是可以用的,用哪个更好一点呢?这个问题本门课不详细展开,我这里选择使用jpg文件来保存图片信息。
另外注意,在open函数中,应选择wb(二进制写)模式,因为我们爬取的是二进制的信息。
open函数
模式 | 作用 | 二进制模式 |
---|---|---|
r(read,读) | 只读,指针在文件开头,文件不存在则报错 | rb二进制读 |
w(write,写) | 只写,文件不存在则新建,存在则覆盖 | wb二进制写 |
a(append,追加) | 追加,文件存在指针放在末尾,文件不存在则新建 | ab二进制追加 |
import requests
url = 'http://weike:5678/static/logo.jpg'
res = requests.get(url)
print(res.status_code) #打印对象的响应状态码,以检查请求是否成功
print(res.content) #打印对象的二进制编码内容
# 新建了一个文件angry.jpg,这里的文件没加路径,它会被保存在程序运行的当前目录下。
# 图片内容需要以二进制wb读写。
photo = open('angry.jpg','wb')
# 获取pic的二进制内容
photo.write(res.content)
# 关闭文件
photo.close()
3.思考
我们上次课发现了网页中的三个txt文件名是有规律的,这个是我故意设计的,这样我们可以很容易的写出一个循环一次性地爬取全部的txt文件,但是这节课我们发现网页中的几张图片地址是没有规律的,如何通过循环遍历?
这个问题我留到后面给大家解答,实际上要先学习网页爬取,通过爬取网页中所有的图片地址,然后存放到一个列表中,之后我们遍历整个列表就可以爬取网页中所有的图片了。
4.课后练习
本网站上还有一个视频,请你根本本节课学习的内容爬取视频文件
提示:视频也是用二进制编码的,另外视频内容我推荐使用mp4类型的文件进行保存。
第三课:网页爬取
1.任务布置
爬取网页中的各监测站点实时数据,要求能够循环依次打印出来即可。
2.思路梳理(爬取网页的基本步骤)
如果我们还是按照之前的老三步走,你也的确能够将网页爬取下来,但是你是将整个网页都爬取下来了,其中有很多是我们不想要的信息,是没有用的垃圾信息,看起来比较恶心,所以爬取网页的话,我们还需要从其中提取出关键的信息,所以爬取网页内容会多两个步骤。
- 找到资源地址
- 发送请求,获取资源
- 解析数据
- 提取数据
- 文件保存
3.任务实战
1.找到资源地址
资源地址就是网页的url,也就是我们俗称的网址。
url = 'http://weike:5678/'
2.发送请求,获取资源
发送请求之后获得响应对象,因为我们想要的是网页中的文本信息,所以打印这个对象的文本内容即可,如果乱码了按照之前讲的方法改变一下编码就解决了。
import requests
url = 'http://weike:5678'
res = requests.get(url)
res.encoding = 'utf-8' # 使用utf-8编码,解决乱码问题
print(res.status_code) # 打印对象的响应状态码,以检查请求是否成功
print(res.text) # 打印网页的文本内容
有的同学很着急,已经使用open函数新建了一个txt文件,将刚才爬取到的网页内容保存起来了。
还记得我们一开始说的吗?在保存文件之前,我们还需要提取出有价值的信息,而不是一股脑的全部保存下来,这样的信息我们是没有办法阅读的。
3.解析数据
我们现在拿到的res.text是一个字符串,不信你可以print(type(res.text))
验证一下,对于一个字符串,我们要从其中提取出我们想要的内容是比较麻烦的,需要使用正则表达式,感兴趣的同学可以自己自学,本门课介绍的更为简单的一种方法,就是先将res.text解析为BS对象。
这里我们用到了bs4中的BeautifulSoup方法,bs4也是一个第三方库,我们在使用之前也需要安装
bs4.BeautifulSoup()方法需要传递两个参数,首先是我们要把待解析的数据给它,这个数据就是我们刚才爬取到的网页文本内容res.text,它是数据HTML内容的字符串。后一个参数是指定解析器,因为我们想要解析的内容是HTMK的数据,所以这里选择html.parser
,它是Python 的标准 HTML 解析器。
import requests
import bs4
url = 'http://weike:5678'
res = requests.get(url)
res.encoding = 'utf-8' # 使用utf-8编码,解决乱码问题
print(res.status_code) # 打印对象的响应状态码,以检查请求是否成功
# print(res.text) # 打印网页的文本内容
# 解析数据
soup = bs4.BeautifulSoup(res.text,'html.parser')
print(soup)
我们打印出解析好的内容soup
,有同学仔细对比之后发现和res.text没有什么区别,那为什么要解析它?解析为BS对象有什么好处呢?
你可以试试print(type(soup))
,发现它虽然内容没变,但是类型却变成了bs4.BeautifulSoup对象。
bs4.BeautifulSoup对象身上有很多好用的方法,比如find方法可以根据网页的标签找到我们想要的内容,接下来我就给大家介绍如何通过这些方法取提取数据。
4.提取数据
bs4.BeautifulSoup对象的find()
和find_all()
方法
方法 | 作用 | 用法 | 示例 |
---|---|---|---|
find() | 提取满足要求的首个数据 | BeautifulSoup对象.find(标签,属性) | soup.find(‘div’,class_=’books’) |
find_all() | 提取满足要求的所有数据 | BeautifulSoup对象.find_all(标签,属性) | soup.find_all(‘div’,class_=’books’) |
通过bs4.BeautifulSoup对象的find()
方法可以定位到网页元素,但是我们需要知道该标签的一些特征,比如说标签的id名或者class名,这里我们通过开发者工具定位到网页元素,找到嘉兴学院`,发现该标签元素有一个类名station,我们可以通过这个类名获取到元素。
# 提取数据
station = soup.find(class_='station')
print(station)
注意:这里的class同时也是python中的关键字,直接写会报错,bs4的开发者也发现了这个问题,我查了他的文档,他推荐我们在class后面加一个下划线以解决这个问题。
如果你只想要这个标签的文本信息,你可以这样写,意思是找到这个标签之后,只要这个标签中的文本内容。
# 提取数据
station = soup.find(class_='station').text
print(station)
最后我们发现其实在这里用find()
不是很合适,因为find()
只会提取满足要求的首个数据,而我们需要提取所有的站点数据,这个时候应该使用find_all()
,因为会获取到很多数据,find_all()
会返还给我们一个列表。我们如果想依次打印的话,还需要遍历这个列表。
import requests
import bs4
url = 'http://weike:5678'
res = requests.get(url)
res.encoding = 'utf-8' # 使用utf-8编码,解决乱码问题
print(res.status_code) # 打印对象的响应状态码,以检查请求是否成功
# print(res.text) # 打印网页的文本内容
# 解析数据
soup = bs4.BeautifulSoup(res.text,'html.parser')
# print(soup)
# print(type(soup))
# 提取数据
rows = soup.find_all(class_='row') # 整行数据
for row in rows:
# 监测站点
station = row.find(class_='station').text
# AQI
AQI = row.find(class_='AQI').text
# PM2.5
PM = row.find(class_='PM').text
# grade
grade = row.find(class_='grade').text
print(station,AQI,PM,grade)
这样就将所有的站点名称都打印出来了。
4.课后练习
剩下的还有AQI、PM2.5、污染等级,请你通过同样的方法将剩下的内容也提取出来,依次打印。
第四课:以excel的方式保存
1.任务布置
将上一课爬取到的空气质量数据保存到excel表格中
2.基础知识讲解
(1)excel文件的写入
import openpyxl
# 1、创建工作簿
# 利用openpyxl.Workbook()创建workbook对象
wb = openpyxl.Workbook()
# 2、获取工作表
# 工作表就是workbook对象的active属性
sheet = wb.active
# 3、设置工作表标题
sheet.title = '天气数据'
# 4、操作单元格
# 单元格:sheet['A1']
sheet['A1'] = '列标题1'
sheet['B1'] = '列标题2'
# 整行操作:append(list)
sheet.append(['数据1','数据2'])
# 5、保存单元薄
wb.save('./练习.xlsx')
(2)excel文件的读取
import openpyxl
# 1、打开工作簿
# 利用openpyxl.load_workbook()创建workbook对象
wb = openpyxl.load_workbook('./练习.xlsx')
# 2、获取工作表
# 通过workbook对象的键wb['sheet'](就是做工作表的表名)
sheet = wb['天气数据']
# 3、读取单元格
# 借助单元格的value属性,sheet['sheet'].value
A1 = sheet['A1'].value
# 4、打印单元格内容
print(A1)
3.任务实战
import requests
import bs4
import openpyxl
# 1、创建工作簿
# 利用openpyxl.Workbook()创建workbook对象
wb = openpyxl.Workbook()
# 2、获取工作表
# 工作表就是workbook对象的active属性
sheet = wb.active
# 3、设置工作表标题
sheet.title = '天气数据'
# 4、操作单元格
# 单元格:sheet['A1']
sheet['A1'] = '监测站点'
sheet['B1'] = 'AQI'
sheet['C1'] = 'PM2.5'
sheet['D1'] = '污染等级'
# 5、爬取数据添加到单元格
url = 'http://weike:5678'
res = requests.get(url)
res.encoding = 'utf-8' # 使用utf-8编码,解决乱码问题
print(res.status_code) # 打印对象的响应状态码,以检查请求是否成功
# print(res.text) # 打印网页的文本内容
# 解析数据
soup = bs4.BeautifulSoup(res.text,'html.parser')
# print(soup)
# print(type(soup))
# 提取数据
rows = soup.find_all(class_='row') # 整行数据
for row in rows:
# 监测站点
station = row.find(class_='station').text
# AQI
AQI = row.find(class_='AQI').text
# PM2.5
PM = row.find(class_='PM').text
# grade
grade = row.find(class_='grade').text
print(station,AQI,PM,grade)
# 工作表保存整行数据:append(list)
sheet.append([station,AQI,PM,grade])
# 5、保存单元薄
wb.save('./嘉兴天气数据.xlsx')
4.总结
(1)excel文件的写入步骤
(2)excel文件的读取步骤
第五课:批量爬取图片
1.任务描述
爬取整个页面上的所有图片
2.思路引导
和本页面上的txt文件不同,图片的地址是没有规律的,我们不能通过循环拼接出地址。
可以尝试先利用网页爬取的方法,爬取所有图片的地址,再根据地址爬取每一张图片。
3.任务实战
import requests
import bs4
# 1、先爬取所有图片的地址,并保存到一个列表中
url = 'http://weike:5678/'
res = requests.get(url)
soup = bs4.BeautifulSoup(res.text,'html.parser')
# 分析网页结构可知,img标签是没有class的,不能通过class名定位到元素
# 具体情况具体分析,由于我们是要爬取所有的img标签,我们可以通过img标签进行定位
imgs = soup.find_all('img')
number = 1 # 图片序号
for img in imgs:
# 图片的地址
src_relative = img['src'] # 这个只是相对路径,.号代替的其实就是网站的根目录,我们还需要补全
src_absolute = 'http://weike:5678/' + src_relative[1:] # 绝对路径,完整的图片地址
print(src_absolute)
# 有了图片的地址就可以开始爬取了
res = requests.get(src_absolute)
# 图片内容需要以二进制wb读写。
photo = open(f'{number}.jpg','wb')
# 获取pic的二进制内容
photo.write(res.content)
# 关闭文件
photo.close()
number += 1