初识爬虫
爬虫的概念
什么是爬虫
爬虫
:通过编写程序,模拟浏览器上网,并抓取有价值的数据的过程
反爬虫
:门户网站通过制定相应的策略或技术手段,来阻止爬虫程序对其网站数据的爬取
反反爬
:爬虫程序可以采用一些技术手段,来绕过或破坏门户网站的反爬机制,从而爬取到有用的数据
爬虫与反爬虫就是一对矛与盾
爬虫合法性探究
爬虫可能带来的风险?
- 爬虫干扰了被访问网站的正常运营
- 爬虫抓取了受到法律保护的特定类型的数据或信息
如何合理地使用爬虫?
- 对爬虫程序进行优化,避免干扰网站的正常运行
- 不要爬取涉及商业机密等敏感信息
爬虫的君子协议
通常,网站的robots.txt
文件中声明了那些数据可以被爬取,那些数据不可以被爬取(非强制性)
爬虫的分类
在不同的使用场景下,爬虫的分类有
通用爬虫
:抓取一整张页面的数据(很可能包含大量无用信息)聚焦爬虫
:抓取页面中特定的局部内容,必须建立在通用爬虫的基础之上增量爬虫
:只会爬取网站中最新更新的数据
网络请求与响应
http协议
http(s)协议是服务器和客户端进行数据交互的一种形式,服务器和客户端都需要遵守该协议才能进行数据交互
https协议是http协议的升级版,服务器与客户端的数据交互是通过证书加密的,攻击者很难获得有价值的信息
常用的请求头信息
Request Header | 描述 |
---|---|
User-Agent | 请求载体的身份标识 |
Connection | 请求完毕后,保持连接还是断开连接 |
常用的响应头信息
Response Header | 描述 |
---|---|
Content-Type | 服务器响应数据的类型 |
requests模块
requests
是python中的一个基于网络请求的模块,用来模拟浏览器发送请求。
requests模块的安装与使用
pip install requests
import requests
url = 'http://www.baidu.com'
resp = requests.get(url) #发起一个get请求,并获得响应数据
page_content = resp.text
print(page_content)
属性 | 描述 |
---|---|
resp.text | 以字符串形式返回,通常是页面的html源代码 |
resp.content | 以二进制形式返回,比如一张图片、一个音频 |
resp.json() | 返回一个字典对象(当响应数据是json类型时使用) |
基于requests的简易网页采集器(使用到了UA伪装)
import requests
# 1. 准备数据
# url = 'https://www.sogou.com/web?query=猫羽雫'
url = 'https://www.sogou.com/web'
word = input('Enter a word:')
params = { #请求参数,拼接在url后
'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'
}
# 2. 发起请求
resp = requests.get(url,params,headers=headers)
resp.encoding = resp.apparent_encoding
page_content = resp.text
print(page_content)
# 3. 持久化存储
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解析器)
pip install bs4
pip install lxml
from bs4 import BeautifulSoup
import requests
# 加载html有两种方式
# 方式一 使用本地html文档中的数据
fp = open('./猫羽雫.html','r',encoding='utf-8')
soup = BeautifulSoup(fp,'lxml')
print(soup) #我们发现,soup对象的内容就是加载到该对象中的html源码
# 方式二 从互联网上获取html源码
url = 'https://www.baidu.com'
resp = requests.get(url)
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text,'lxml')
print(soup)
BeautifulSoup对象中提供的属性和方法
1.根据标签名或选择器定位,返回标签之间的所有内容
方法 | 描述 |
---|---|
find(tag,attr=value) | 根据标签名和属性进行定位,只返回符合条件的第一个元素内容 |
find_all(tag,attr=value) | 返回一个列表,用法同上 |
select(css选择器) | 使用CSS选择器进行定位,返回一个列表 |
2.获取标签之间的文本数据(不包括子标签)
属性 | 描述 |
---|---|
.text | 获取所有文本内容 |
.string | 只能获取直系的文本内容 |
3.获取标签中指定属性的值
属性 | 描述 |
---|---|
[attr] | 获取属性对应的属性值 |
使用xpath解析
xpath解析是最常用、最便捷、最高效,且通用性最强的解析方式。xpath是根据元素所处层级进行定位的
xpath解析的一般步骤
1.实例化一个etree对象,并将要解析的html页面源码数据加载到对象中
2.调用etree对象的xpath(xpath表达式)
方法结合xpath表达式实现标签的定位和数据提取
xpath的安装与使用
pip install lxml
from lxml import etree
import requests
# 加载html有两种方式
# 方式一 使用本地html文档中的数据
etree.parse('./猫羽雫.html')
# 方式二 从互联网上获取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表达式用法
1.根据html元素层级进行定位
xpath表达式 | 描述 |
---|---|
/ | 表示单个层级,放在开头表示html根元素 |
// | 表示多个层级(常用) |
./ | 表示当前标签 |
[@attr=value] | 根据属性进行定位 |
[index] | 根据元素所处位置进行定位,index从1开始 |
2.提取标签之间的文本数据
xpath表达式 | 描述 |
---|---|
/text() | 获取标签中的文本,返回一个列表 |
//text() | 可以获取标签中非直系的文本,返回一个列表 |
3.提取标签中指定属性的值
xpath表达式 | 描述 |
---|---|
/@attr | 获取标签中属性对应的值 |
数据解析实战
我们将通过三个案例分别使用三种数据解析方式获取到我们想要的数据
彼岸图网
需求
:获取图中每个li标签内,所有a标签href属性的值,所有img标签src属性的值,以及所有b标签内的文本
方式一 正则表达式
import requests
import re
# 1. 准备数据
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 = re.findall(ex0,page_content,re.S) #实际上页面中就一个符合条件的ul
ul_content = ul[0].strip() #strip()去除字符串首尾空格和换行
# 2. 数据解析
# 待匹配的字符串格式:- 4k 居家 可爱 公主 女孩
ex = '- (.*?)
' #把想要提取的内容用()括起来
data_list = re.findall(ex,ul_content,re.S)
for data in data_list:
print(data)
# 输出结果
# ('/tupian/30912.html', '/uploads/allimg/230111/012011-16733712117326.jpg', '4k 居家 可爱 公主 女孩')
# ('/tupian/30903.html', '/uploads/allimg/230110/153047-16733358474a9c.jpg', '甜美微笑 双手合十 小清')
# ... ...
方式二 bs4
import requests
from bs4 import BeautifulSoup
# 1. 准备数据
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
# 2. 数据解析
soup = BeautifulSoup(page_content,'lxml')
ul = soup.find('ul',class_='clearfix')
a_list = ul.find_all('a')
# 或者直接 a_list = soup.select('div.clearfix li 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 #text获取标签中的所有文本
print(b)
# 输出结果
# /tupian/30912.html
# /uploads/allimg/230111/012011-16733712117326.jpg
# 4k 居家 可爱 公主 女孩
# /tupian/30903.html
# /uploads/allimg/230110/153047-16733358474a9c.jpg
# 甜美微笑 双手合十 小清
# ... ...
方式三 xpath
from lxml import etree
import requests
# 1. 准备数据
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
# 2. 数据解析
# //多个层级下找 xpath()永远返回一个列表
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)
# 输出结果
# /tupian/30912.html
# /uploads/allimg/230111/012011-16733712117326.jpg
# 4k 居家 可爱 公主 女孩
# /tupian/30903.html
# /uploads/allimg/230110/153047-16733358474a9c.jpg
# 甜美微笑 双手合十 小清
# ... ...
Wallhaven
需求
:获取图中每个li标签内,所有a标签href属性的值,所有img标签data-src属性的值,以及所有span标签内图片的分辨率信息
方式一 正则表达式
import requests
import re
# 1. 准备数据
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
# 2. 数据解析
ex = '(.*?)'
data_list = re.findall(ex,page_content,re.S)
for data in data_list:
print(data)
# 输出结果
# ('https://th.wallhaven.cc/small/l8/l83o92.jpg', 'https://wallhaven.cc/w/l83o92', '1920 x 1200')
# ('https://th.wallhaven.cc/small/85/85o67y.jpg', 'https://wallhaven.cc/w/85o67y', '825 x 1400')
# ... ...
方式二 bs4
import requests
from bs4 import BeautifulSoup
# 1. 准备数据
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
# 2. 数据解析
# select()选择器定位 find_all()标签名+属性定位 他们都返回一个列表 |find()返回字符串类型
# [attr]不能直接从列表中获取属性的值
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 #text获取标签内所有文本 string获取标签内直系文本
print(span)
# 输出结果
# https://th.wallhaven.cc/small/l8/l83o92.jpg
# https://wallhaven.cc/w/l83o92
# 1920 x 1200
# https://th.wallhaven.cc/small/85/85o67y.jpg
# https://wallhaven.cc/w/85o67y
# 825 x 1400
# ... ...
方式三 xpath
from lxml import etree
import requests
# 1. 准备数据
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
# 2. 数据解析
tree = etree.HTML(page_content)
figure_list = tree.xpath('//figure') #在文档中查找figure标签,注意xpath()方法永远返回一个列表
for figure in figure_list:
img_data_src = figure.xpath('./img/@data-src')[0] #获取当前标签下的img标签的data-src属性的值
print(img_data_src)
a_href = figure.xpath('./a/@href')[0]
print(a_href)
span = figure.xpath('.//span[@class="wall-res"]/text()')[0] # //在多个层级下寻找 /text()获取标签内的文本
print(span)
# 输出结果
# https://th.wallhaven.cc/small/l8/l83o92.jpg
# https://wallhaven.cc/w/l83o92
# 1920 x 1200
# https://th.wallhaven.cc/small/85/85o67y.jpg
# https://wallhaven.cc/w/85o67y
# 825 x 1400
# ... ...
爬虫实战(进阶)
观察发现,每个图片都放在一个li标签中
import requests
import re
import os
import time
# 获取整个网页源代码 :给我一个url,返回页面源代码
def getPageContent(url):
resp = requests.get(url,headers=headers)
resp.encoding = resp.apparent_encoding #解决页面中文乱码,一定要在resp.text前调用
page_content = resp.text
return page_content;
# 从父页面源代码中解析出每个子页面的url
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 = '.*? '
# dir = input('请输入要将图片保存在那个路径下(如D:\\img):')
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 = '
本文由博客一文多发平台 OpenWrite 发布!