【python 爬虫详解】用三个案例,搞懂 python 爬虫

初识爬虫

爬虫的概念

什么是爬虫

爬虫:通过编写程序,模拟浏览器上网,并抓取有价值的数据的过程

反爬虫:门户网站通过制定相应的策略或技术手段,来阻止爬虫程序对其网站数据的爬取

反反爬:爬虫程序可以采用一些技术手段,来绕过或破坏门户网站的反爬机制,从而爬取到有用的数据

爬虫与反爬虫就是一对矛与盾

爬虫合法性探究

爬虫可能带来的风险?

  • 爬虫干扰了被访问网站的正常运营
  • 爬虫抓取了受到法律保护的特定类型的数据或信息

如何合理地使用爬虫?

  • 对爬虫程序进行优化,避免干扰网站的正常运行
  • 不要爬取涉及商业机密等敏感信息

爬虫的君子协议

通常,网站的robots.txt文件中声明了那些数据可以被爬取,那些数据不可以被爬取(非强制性)

20230113122447

爬虫的分类

在不同的使用场景下,爬虫的分类有

  1. 通用爬虫:抓取一整张页面的数据(很可能包含大量无用信息)

  2. 聚焦爬虫:抓取页面中特定的局部内容,必须建立在通用爬虫的基础之上

  3. 增量爬虫:只会爬取网站中最新更新的数据

网络请求与响应

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,'保存成功!')

数据解析

数据解析是在得到整个网页源代码后,对其中的有用信息进行提取的过程。属于聚焦爬虫

数据解析的一般步骤

检查网页源代码发现,有价值的数据一般存放在标签中,或者标签的属性中。所以数据解析的一般步骤是:<mark>1.获取网页源代码 2.标签定位 3.解析数据</mark>

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的一般步骤

  1. 实例化一个BeautifulSoup对象,并且<mark>将页面源代码加载到该对象中</mark>
  2. 调用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选择器进行定位,返回一个列表


  1. 获取标签之间的文本数据(不包括子标签)
属性 描述
.text 获取所有文本内容
.string 只能获取直系的文本内容


  1. 获取标签中指定属性的值
属性 描述
[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开始


  1. 提取标签之间的文本数据
xpath表达式 描述
/text() 获取标签中的文本,返回一个列表
//text() 可以获取标签中非直系的文本,返回一个列表


  1. 提取标签中指定属性的值
xpath表达式 描述
/@attr 获取标签中属性对应的值


数据解析实战

我们将通过三个案例分别使用三种数据解析方式获取到我们想要的数据

彼岸图网

需求:获取图中每个li标签内,所有a标签href属性的值,所有img标签src属性的值,以及所有b标签内的文本

20230114184112

方式一 正则表达式

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

# 为了防止下一步获取的<li>标签不准确,这里加了一步先获取<ul>的操作
ex0 = '<ul class="clearfix">(.*?)</ul>'
ul = re.findall(ex0,page_content,re.S)  #实际上页面中就一个符合条件的ul
ul_content = ul[0].strip()  #strip()去除字符串首尾空格和换行

# 2. 数据解析
# 待匹配的字符串格式:<li><a href="/tupian/30912.html" target="_blank"><img src="/uploads/allimg/230111/012011-16733712117326.jpg" alt="4k 居家 可爱 公主 女孩 美女 粉色裙子 电脑 壁纸" /><b>4k 居家 可爱 公主 女孩</b></a></li>
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)

# 输出结果
# ('/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标签内图片的分辨率信息

20230114194136

方式一 正则表达式

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 = '<img .*? data-src="(.*?)" .*?<a .*? href="(.*?)" .*?<span .*?>(.*?)</span>'
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
# ... ... 

爬虫实战(进阶)

20230113154149

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

20230113160905
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 = '<li><a href="(.*?)" .*?<b>.*?</b></a></li>'

# 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 = '<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)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容