初识爬虫
爬虫的概念
什么是爬虫
爬虫
:通过编写程序,模拟浏览器上网,并抓取有价值的数据的过程
反爬虫
:门户网站通过制定相应的策略或技术手段,来阻止爬虫程序对其网站数据的爬取
反反爬
:爬虫程序可以采用一些技术手段,来绕过或破坏门户网站的反爬机制,从而爬取到有用的数据
爬虫与反爬虫就是一对矛与盾
爬虫合法性探究
爬虫可能带来的风险?
- 爬虫干扰了被访问网站的正常运营
- 爬虫抓取了受到法律保护的特定类型的数据或信息
如何合理地使用爬虫?
- 对爬虫程序进行优化,避免干扰网站的正常运行
- 不要爬取涉及商业机密等敏感信息
爬虫的君子协议
通常,网站的robots.txt
文件中声明了那些数据可以被爬取,那些数据不可以被爬取(非强制性)

爬虫的分类
在不同的使用场景下,爬虫的分类有
-
通用爬虫
:抓取一整张页面的数据(很可能包含大量无用信息)
-
聚焦爬虫
:抓取页面中特定的局部内容,必须建立在通用爬虫的基础之上
-
增量爬虫
:只会爬取网站中最新更新的数据
网络请求与响应
http协议
http(s)协议是服务器和客户端进行数据交互的一种形式,服务器和客户端都需要遵守该协议才能进行数据交互
https协议是http协议的升级版,服务器与客户端的数据交互是通过证书加密的,攻击者很难获得有价值的信息
常用的请求头信息
Request Header |
描述 |
User-Agent |
请求载体的身份标识 |
Connection |
请求完毕后,保持连接还是断开连接 |
常用的响应头信息
Response Header |
描述 |
Content-Type |
服务器响应数据的类型 |
requests模块
requests
是python中的一个基于网络请求的模块,用来模拟浏览器发送请求。
requests模块的安装与使用
1 2 3 4 5 6
| import requests
url = 'http://www.baidu.com' resp = requests.get(url) page_content = resp.text print(page_content)
|
属性 |
描述 |
resp.text |
以字符串形式返回,通常是页面的html源代码 |
resp.content |
以二进制形式返回,比如一张图片、一个音频 |
resp.json() |
返回一个字典对象(当响应数据是json类型时使用) |
基于requests的简易网页采集器(使用到了UA伪装)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import requests
url = 'https://www.sogou.com/web' word = input('Enter a word:') params = { 'query': word } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' }
resp = requests.get(url,params,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text print(page_content)
file_name = word + '.html' with open(file_name,'w',encoding='utf-8') as fp: fp.write(page_content) print(file_name,'保存成功!')
|
数据解析
数据解析
是在得到整个网页源代码后,对其中的有用信息进行提取的过程。属于聚焦爬虫
数据解析的一般步骤
检查网页源代码发现,有价值的数据一般存放在标签中,或者标签的属性中。所以数据解析的一般步骤是:1.获取网页源代码 2.标签定位 3.解析数据
F12检查元素中的数据不一定在页面源代码中,也有可能是通过ajax动态刷新的数据,这是我们在数据解析时需要注意的。数据解析要以页面源代码为准!
Python中数据解析的三种方式
1.正则表达式(通用) 2.BeautifulSoup4(python独有) 3.xpath(推荐,通用性最强)
使用正则表达式
建议先把要提取的那部分源码单独复制,对照着去写正则表达式(F12太乱了🤣)
从重复的标签(如li)开始写正则,那么这个正则可以提取到多组数据哦
re.findall(pattern,string,flags)
参数 |
描述 |
pattern |
匹配的正则表达式 |
string |
待匹配的文本字符串 |
flags |
标志位。用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等 |
标志位 |
描述 |
re.S |
使.能够匹配包括换行在内的所有字符 |
re.M |
多行匹配,影响^和$ |
re.I |
使匹配对大小写不敏感 |
正则表达式 |
描述 |
. |
匹配除换行符外的任意单个字符 |
* |
匹配多个字符 |
? |
非贪婪匹配 |
使用BeautifulSoup
对于一个网页来说,都有一定的特殊结构和层级关系,而且很多节点都用id和class来区分。所以可以借助网页的结构和属性来提取数据。
使用BeautifulSoup的一般步骤
- 实例化一个BeautifulSoup对象,并且将页面源代码加载到该对象中
- 调用BeautifulSoup对象提供的属性或方法进行标签定位和数据提取
BeautifulSoup4的安装与使用(需要一并下载lxml解析器)
1 2
| pip install bs4 pip install lxml
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| from bs4 import BeautifulSoup import requests
fp = open('./猫羽雫.html','r',encoding='utf-8') soup = BeautifulSoup(fp,'lxml') print(soup)
url = 'https://www.baidu.com' resp = requests.get(url) resp.encoding = resp.apparent_encoding soup = BeautifulSoup(resp.text,'lxml') print(soup)
|
BeautifulSoup对象中提供的属性和方法
- 根据标签名或选择器定位,返回标签之间的所有内容
方法 |
描述 |
find(tag,attr=value) |
根据标签名和属性进行定位,只返回符合条件的第一个元素内容 |
find_all(tag,attr=value) |
返回一个列表,用法同上 |
select(css选择器) |
使用CSS选择器进行定位,返回一个列表 |
2. 获取标签之间的文本数据(不包括子标签)
属性 |
描述 |
.text |
获取所有文本内容 |
.string |
只能获取直系的文本内容 |
3. 获取标签中指定属性的值
使用xpath解析
xpath解析是最常用、最便捷、最高效,且通用性最强的解析方式。xpath是根据元素所处层级进行定位的
xpath解析的一般步骤
- 实例化一个etree对象,并将要解析的html页面源码数据加载到对象中
- 调用etree对象的
xpath(xpath表达式)
方法结合xpath表达式实现标签的定位和数据提取
xpath的安装与使用
1 2 3 4 5 6 7 8 9 10 11 12
| from lxml import etree import requests
etree.parse('./猫羽雫.html')
url = 'https://www.baidu.com' resp = requests.get(url) resp.encoding = resp.apparent_encoding etree.HTML(page_content)
|
方法 |
描述 |
etree.HTML(html) |
实例化一个etree对象,并加载要解析的html |
etree.parse(filepath) |
实例化一个etree对象,并加载要解析的html(本地html) |
xpath(xpath表达式) |
使用xpath表达式进行标签定位,返回一个列表 |
xpath()方法返回一个列表,如果xpath表达式只进行了定位,没有进行数据提取,那么列表中每个元素都将是一个Element对象。
xpath表达式用法
- 根据html元素层级进行定位
xpath表达式 |
描述 |
/ |
表示单个层级,放在开头表示html根元素 |
// |
表示多个层级(常用) |
./ |
表示当前标签 |
[@attr=value] |
根据属性进行定位 |
[index] |
根据元素所处位置进行定位,index从1开始 |
- 提取标签之间的文本数据
xpath表达式 |
描述 |
/text() |
获取标签中的文本,返回一个列表 |
//text() |
可以获取标签中非直系的文本,返回一个列表 |
- 提取标签中指定属性的值
xpath表达式 |
描述 |
/@attr |
获取标签中属性对应的值 |
数据解析实战
我们将通过三个案例分别使用三种数据解析方式获取到我们想要的数据
彼岸图网
需求
:获取图中每个li标签内,所有a标签href属性的值,所有img标签src属性的值,以及所有b标签内的文本

方式一 正则表达式
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
| import requests import re
url = 'https://pic.netbian.com/4kmeinv/' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' } resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text
ex0 = '<ul class="clearfix">(.*?)</ul>' ul = re.findall(ex0,page_content,re.S) ul_content = ul[0].strip()
ex = '<li><a href="(.*?)" .*?<img src="(.*?)" .*?<b>(.*?)</b></a></li>' data_list = re.findall(ex,ul_content,re.S) for data in data_list: print(data)
|
方式二 bs4
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
| import requests from bs4 import BeautifulSoup
url = 'https://pic.netbian.com/4kmeinv/' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' } resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text
soup = BeautifulSoup(page_content,'lxml') ul = soup.find('ul',class_='clearfix') a_list = ul.find_all('a')
for a in a_list: a_href = a['href'] print(a_href)
img_src = a.find('img')['src'] print(img_src)
b = a.find('b').text print(b)
|
方式三 xpath
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
| from lxml import etree import requests
url = 'https://pic.netbian.com/4kmeinv/' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' } resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text
tree = etree.HTML(page_content) li_list = tree.xpath('//ul[@class="clearfix"]/li')
for li in li_list: a_href = li.xpath('./a/@href')[0] print(a_href)
img_src = li.xpath('.//img/@src')[0] print(img_src)
span = li.xpath('.//b/text()')[0] print(span)
|
Wallhaven
需求
:获取图中每个li标签内,所有a标签href属性的值,所有img标签data-src属性的值,以及所有span标签内图片的分辨率信息

方式一 正则表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import requests import re
url = 'https://wallhaven.cc/toplist' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' } resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text
ex = '<img .*? data-src="(.*?)" .*?<a .*? href="(.*?)" .*?<span .*?>(.*?)</span>' data_list = re.findall(ex,page_content,re.S) for data in data_list: print(data)
|
方式二 bs4
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
| import requests from bs4 import BeautifulSoup
url = 'https://wallhaven.cc/toplist' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' } resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text
soup = BeautifulSoup(page_content,'lxml') figure_list = soup.select('figure.thumb')
for figure in figure_list:
img_data_src = figure.find('img')['data-src'] print(img_data_src)
a_href = figure.find('a')['href'] print(a_href)
span = figure.find('span').text print(span)
|
方式三 xpath
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
| from lxml import etree import requests
url = 'https://wallhaven.cc/toplist' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' } resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text
tree = etree.HTML(page_content) figure_list = tree.xpath('//figure')
for figure in figure_list: img_data_src = figure.xpath('./img/@data-src')[0] print(img_data_src)
a_href = figure.xpath('./a/@href')[0] print(a_href)
span = figure.xpath('.//span[@class="wall-res"]/text()')[0] print(span)
|
爬虫实战(进阶)

观察发现,每个图片都放在一个li标签中

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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| import requests import re import os import time
def getPageContent(url): resp = requests.get(url,headers=headers) resp.encoding = resp.apparent_encoding page_content = resp.text return page_content;
def jiexi(ex,page_content): child_a_list = re.findall(ex,page_content,re.S) child_a_list = child_a_list[1:] return child_a_list;
def jiexi2(ex,page_content): img_a_list = re.findall(ex, page_content, re.S) return img_a_list;
def saveImg(dir,img_url): nowtime = time.strftime("%Y%m%d-%H%M%S", time.localtime()) filename = dir + '\\' + nowtime + '.jpg'
resp = requests.get(img_url, headers=headers) with open(filename, 'wb') as fp: fp.write(resp.content) print(filename, '保存成功!')
url = f'https://pic.netbian.com/4kmeinv/index_2.html' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36' }
ex = '<li><a href="(.*?)" .*?<b>.*?</b></a></li>'
dir = 'C:\\Users\\编程小白\\Pictures\\photo\\bian2' if not os.path.exists(dir): os.mkdir(dir) else: print('该文件夹已经存在,不需要再创建')
for page in range(2,22): print('正在爬取第', page, '页图片...') url = f'https://pic.netbian.com/4kdongwu/index_{page}.html' page_content = getPageContent(url) child_a_list = jiexi(ex, page_content)
for child_a in child_a_list: child_url = 'https://pic.netbian.com'+ child_a child_page_content = getPageContent(child_url) child_ex = '<a href="" id="img"><img src="(.*?)"' img_a_list = jiexi2(child_ex,child_page_content)
for img_a in img_a_list: img_url = 'https://pic.netbian.com' + img_a print(img_url) saveImg(dir,img_url)
time.sleep(2)
|