您当前的位置:首页 > 计算机 > 编程开发 > Python

Python爬虫 之 聚焦爬虫(数据解析 正则,bs4,xpath)

时间:10-19来源:作者:点击数:

Python爬虫 之 聚焦爬虫(数据解析)

聚焦爬虫:简单的来说就是爬取目标界面中特定的数据。对于一整页网页数据,我们有时候想要的内容仅仅是某些特定的信息,就像我们拿到某页的网址,仅仅是想要里面的小姐姐的图片,而不想要其他的。那么我们如何针对性的爬取我们所想要的信息呢???那么我们就需要用到数据解析帮助我们

数据解析理论基础

数据解析类别
  • 正则数据解析(支持多种语言)
  • bs4(python独有)
  • xpath(重点讲解)(支持多种语言)
数据解析原理:
  • 解析的局部文本内容都会在标签之间或者标签对应的属性中进行存储。
  • 进行指定标签的定位。
  • 标签或者标签对应的属性中储存数据值进行提取(解析)

聚爬编码流程:

  1. 指定url
  2. 发起请求
  3. 获取响应数据
  4. 数据解析
  5. 持续化储存

举例test.html文件

<html lang="aa">
<head>
    <meta charset="UTF-8">
    <title>测试bs4</title>
</head>
<body>
    <div>
        <p>李一桐</p>
    </div>
    <div class="beautiful_girl" >
        <p>孙一宁</p>
        <p>电气鼠</p>
        <p>刘亦菲</p>
        <p>迪丽热巴</p>
        <a href="https://www.baidu.com/s?wd=迪丽热巴" title="迪丽热巴" target="_self">
            <span>this is a beautiful girl</span>
            代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
        </a>
        <img src="https://gimg2.baidu.com/image_search/" alt="" />
    </div>
    <div class="beautiful_girl" id="legs">
        <ul>
            <li><a herf="https://www.baidu.com/s?wd=李一桐" title="xiao_li">代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩</a></li>
            <li><a herf="https://www.baidu.com/s?wd=杨幂" title="xiao_li">代表作品:三生三世十里桃花、宫锁心玉、小时代</a></li>
            <li><a herf="https://www.baidu.com/s?wd=杨紫" title="xiao_li">代表作品:家有儿女、战长沙、欢乐颂、香蜜沉沉烬如霜</a></li>
            <li><b>i love you</b></li>
            <li><i>you love me</i></li>
            <li><a herf="https://www.baidu.com/s?wd=郑爽" title="xiao_li">代表作品:一起来看流星雨、微微一笑很倾城、夏至未至</a></li>
        </ul>
    </div>
</body>
</html>

注:以后遇到test.html都是指该文件

正则模块

认识正则

正则网址

正则表达式是对字符串操作的一种逻辑公式,我们一般使用正则表达式对于字符串进行匹配和过滤

优点为灵活,功能性强,逻辑性强

正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义,我们下面会给予解释。

要想真正的用好正则表达式,正确的理解元字符是最重要的事情。下表列出了常用的的元字符和对它们的一个简短的描述。

元字符 描述
. 匹配除换行意外的任意字符
\w 匹配字母或数字或下划线
\W 匹配非字母或数字或下划线
\s 匹配任意空字符
\S 匹配非空字符
\d 匹配数字
\D 匹配非数字
\n 匹配换行符
\t 匹配制表符
^ 匹配字符串开始位置
$ 匹配字符串结束位置
a\b(\是竖直的) a或者b(a,b任意)
() 匹配括号内的表达式也表示一个组优先级强
[…] 字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”
[a-z] 字符范围。匹配指定范围内的任意字符
[^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符
量词 含义 栗子
* 重复0次和多次 a* 在aaba中匹配结果aa\n\na
+ 重复1次和多次 a+ 在aaba中匹配结果aa\na
? 重复1次和0次 a? 在aaba中匹配结果\na
{N} 重复N次 a{2} 在aabaaa中匹配结果aa
{N,} 重复N次或更多次 a{2,} 在aabaaa中匹配结果aa\naaa
{N,M} 重复N次到M次 a{2,3} 在aaabaaaa中匹配结果aaa

量词只作用于前面一个字符(栗子中的\n表示换行)

贪婪匹配和惰性匹配

量词中的? * + {} 都属于贪婪匹配(尽可能多的匹配)

.*(贪婪):
reg:看.*妹妹
str:看漂亮的妹妹,姐姐没有妹妹好看!!!
匹配结果:看漂亮的妹妹,姐姐没有妹妹

.*?(惰性):尽可能少的匹配()
reg:看.*?妹妹
str:看漂亮的妹妹,姐姐没有妹妹好看!!!
匹配结果:看漂亮的妹妹

那么我们如何使用正则表达式?

那么开始拿起滑板车,进入re模块

re模块使用正则
import re

# example1:从一个字符串中提取所有的数字
re_1=re.findall("\d+"# 正则表达式
				,"你腿有多长?1米8!"# str
				)
print(re_1)# 返回列表[1,8]

re.findall("你.*?米\d+","你腿有多长?1米8!")
# 结果:['你腿有多长?1米8']
re.findall("你.*?米(\d+)","你腿有多长?1米8!")
#结果:['8']
re.findall("你.*?多(.*?\d+)","你腿有多长?1米8!")
#结果:['长?1']
优先储存()内部数据


# example2:判断一句话是否有数字
re_2=re.rearch("\d+"
		  ,"你腿有多长?1米8!"
		  )
#re_2为Match对象
print(re_2.group())# 1
#只匹配第一个,获取不到第二个


# example3:
re_3=re.finditer("\d+"
				,"你腿有多长?1米8!"
				)
#re_3为迭代器,内部为Match对象
for i in re_3:
	print(i.group())
#1  8 
#匹配所有,只不过返回迭代器


#match匹配,必须从开头 自动在正则表达式前加上 ^
re_4=re.match("\d","你腿有多长?1米8!")
print(re_4)#none
re_4=re.match("\d","1米8!")
print(re_4.group())# 1
#返回一个迭代器


# split分割
re_5=re.split("\d+","我喜欢80的老头,有200块钱的低保")
print(re_5)#['我喜欢','的老头,有','块钱的低保']
re_6=re.split("[的钱]","我喜欢80的老头,有200块钱的低保")
print(re_6)#['我喜欢80','老头,有200块','钱','低保']

# sub替换
re_7=re.sub("\d+","_sb_","你真2,小3")
print(re_7)#你真_sb_,小_sb_


# complie实例化
obj=re.complie("\d+")#实例化
re_8=obj.sub("_sb_","你真2,小3")
print(re_8)#你真_sb_,小_sb_
# 注:在complie实例化对象中有以上提到的所有方法函数,只是举一个sub的栗子


# !!!!!!可能上面用到的不多!但是一下:
#1.re.compile中括号括起来的内容是你自己最终想要得到的结果
#2.(?P<Name>正则) 把正则匹配到的内容直接放在内容Name里面,后面取数据的时候直接可用group("Name"),groupdict()等取出
obj=re.compile(r"今天爱你(?P<you>\d+)次,喜她(?P<she>\d+)次")
re_9=obj.finditer("今天爱你2次,喜她99次")
for i in re_9:
	print(item.group("you"))
	print(item.group("she"))
	print(item.groupdict())#字典格式输出

为避免转译字符的来回转换,我们经常在字符串前面加上 r 来减少不必要的麻烦

爬虫常用!!!:

 <div class="beautiful_girl" id="legs">
        <ul>
            <li><a herf="https://www.baidu.com/s?wd=李一桐" title="xiao_li">代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩</a></li>
            <li><a herf="https://www.baidu.com/s?wd=杨幂" title="xiao_li">代表作品:三生三世十里桃花、宫锁心玉、小时代</a></li>


我们在这里使用爬虫经常用到的方式进行举例:
在 test.html 文件中,
我们要解析出 <div class="beautiful_girl" id="legs">
下的"https://www.baidu.com/s?wd=李一桐" 
和
"https://www.baidu.com/s?wd=杨幂" 
我们该怎么做呢?

reg=" <div class="beautiful_girl" id="legs">.*?<li><a herf=(.*?)title=.*?<li><a herf=(.*?)title="
#对于这个表达式可能不好理解
#可以这样说若没有()则在后续函数匹配时返回reg的所有相匹配的内容
#若有(),则用reg匹配,但是返回()内部匹配的内容


re.findall(reg # 正则表达式
		   ,response # 目标html文件内容(text类)
		   ,re.S#允许跨行匹配
		   )
返回:
["https://www.baidu.com/s?wd=李一桐" ,
"https://www.baidu.com/s?wd=杨幂" ]

在这里,有小伙伴就说了:我TM就想搞图搞图,欣赏美!!!还让我学那么难的正则。。。

哎!这不来了吗?(离你想要的越来越近)

简单图片视频爬取(无正则)
import requests
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
#图片所在链接
url_1 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fn.sinaimg.cn%2Fsinakd20200713ac%2F430%2Fw640h590%2F20200713%2Fa3a5-iwhseiu0954992.jpg&refer=http%3A%2F%2Fn.sinaimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1626771936&t=92facc169d2174c9f47dee97426da86a"

#由于图片是二进制文件,所以要用content转化类型
re=requests.get(url_1,headers=header).content
#打开文件时要以二进制形式打开,文件命名也是图片类型的文件
fp=open("qiu_tu.jpg",mode="wb+",)
fp.write(re)
fp.close()
print("爬爬爬over")

result:

在这里插入图片描述

嘿嘿嘿!!!我懂你,是不是突然感觉图片不香了???

笑

安排!!!

import requests
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
url_1 = "https://qiubai-video-web.qiushibaike.com/EAC0SM23QYZMK7OA_org.mp4"
#二进制文件
re=requests.get(url_1,headers=header).content
#mp4格式
fp=open("qiu_tu.mp4",mode="wb+",)
fp.write(re)
fp.close()
print("爬爬爬over")

result:

发撒

神马?一张一张不劲爆???

在这里插入图片描述
批量图片爬取(正则)
import re#正则
import os #生成文件夹
import requests
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
url_1 = "https://www.qiushibaike.com/imgrank/"


#你可能不知道这一部分怎么来的,来我教你,先放在代码后面了,可以先看后面在回头接着看
"""
<div class="thumb">

<a href="/article/124445723" target="_blank">
<img src="//pic.qiushibaike.com/system/pictures/12444/124445723/medium/KXIMOD0SLOKAI8N7.jpg" alt="糗事#124445723" class="illustration" height="auto">
</a>
</div>
"""
#正则表达式
reg='<div class="thumb">.*?<img src="(.*?)" alt=.*?</div>'
#<div class="thumb">  表示开始位置
#.*?惰性匹配,不同图片不一样,所以要.*
#<img src="  定为地址前位置
#(.*?) 加括号的惰性匹配,也是返回的内容(因为有括号)
#" alt=  定为地址后位置
#.*?惰性匹配,不同图片不一样,所以要.*
#</div> 表示总体结束位置

#获取界面数据供正则解析
result=requests.get(url_1,headers=header).text
src_list=re.findall(reg#正则表达式,返回正则表达式括号括起来的内容
                    ,result#正则目标
                    ,re.S#可跨行匹配
                    )

#因为文件较多,建立文件夹qiu_tu
if not os.path.exists("./qiu_tu"):
    os.mkdir("./qiu_tu")

for i,src in enumerate(src_list):
    #我们发现src_list元素'//pic.qiushibaike.com/system/pictures/12444/124445460/medium/I0DJDSL6O5BL99FU.jpg'
    #缺少http:   ,故要添加
    src_list[i]='http:'+src_list[i]
    #src='http:'+src  不能完成该任务(暂不知原因)

    #爬取二进制图片数据
    image_data=requests.get(url=src_list[i],headers=header).content
    #对于文件不知怎么命名,就用网址最后的数据命名
    image_name=src_list[i].split('/')[-1]
    #确定图片存放路径
    image_path='./qiu_tu/'+image_name
    #wb+方式打开
    fp=open(image_path,mode="wb+")
    fp.write(image_data)
    fp.close()
    print(image_name+" had been over !")

当我们的鼠标放在前端代码之中,选中部分代码时,那就是我们想要的代码,然后复制,对其分析:

在这里插入图片描述

结果:

在这里插入图片描述

小伙伴们一定要联网哦,不然……

在这里插入图片描述

不是吧!你想要更多这图片资源?

那么开始之前我们先了解一个语法:format格式化

res="我高{0}cm,体重{1}kg".format(178,62)
#res为 '我高178cm,体重62kg'

#我们可以这样表达
str="我高{0}cm,体重{1}kg"
height=178
weight=62
res=str.format(height,weight)
#res为 '我高178cm,体重62kg'

#另一种表达
str="我高%dcm,体重%dkg"
height=178
weight=62
res=format(str%(height,weight))#若为一个可省略()
#res为 '我高178cm,体重62kg'

接下来就可以对上一个带代码进行修改了,但是我们要预先了解不同页网址之间的不同

第一页1

第二页2

第三页3

我们发现都出现page/2/和page/3/,那这样第一页是不是是page/1/也可以啊

11

确实可以

所以网址统一为:

https://www.qiushibaike.com/imgrank/page/pageNum/
pageNum为页数%d格式:
根据format两种用法:
https://www.qiushibaike.com/imgrank/page/%d/
https://www.qiushibaike.com/imgrank/page/{0}/
import re#正则
import os #生成文件夹
import requests
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
url_1 = "https://www.qiushibaike.com/imgrank/page/{0}/"

if not os.path.exists("./qiu_tu"):
    os.mkdir("./qiu_tu")
    
for pageNum in range(1,6):
	#爬取1——5页图片
	#新url
    new_url=url_1.format(pageNum)
    reg='<div class="thumb">.*?<img src="(.*?)" alt=.*?</div>'
    
    result=requests.get(new_url,headers=header).text
    src_list=re.findall(reg#正则表达式
                        ,result#正则目标
                        ,re.S#可跨行匹配
                        )
    

    
    for i,src in enumerate(src_list):
        src_list[i]='http:'+src_list[i]
    
        #爬取二进制图片数据
        image_data=requests.get(url=src_list[i],headers=header).content
        #对于文件不知怎么命名,就用网址最后的数据命名
        image_name=src_list[i].split('/')[-1]
        #确定图片存放路径
        image_path='./qiu_tu/'+image_name
        #wb+方式打开
        fp=open(image_path,mode="wb+")
        fp.write(image_data)
        fp.close()
        print(image_name+" had been over !")

好了,快登录你珍藏已久的网站开始吧!嘻嘻嘻

bs4模块

bs4预备知识
bs4数据解析原理
  1. 实例化BeautifulSoup对象,并将数据源码加载进对象中
  2. 通过调用BeautifulSoup对象,将相关的属性和方法进行标签定位和数据提取
BeautifulSoup对象认识和使用
BeautifulSoup对象实例化:
from bs4 import BeautifulSoup

1. 讲本地html文档加载进该对象中
#假设存在test.html目标文件
fp=open("./test.html",mode="r",encoding="utf-8")
soup = BeautifulSoup(fp#文件
					,"lxml"#lxml解析器进行解析
					)
					
2. 将互联网上的页面源码加载到对象中(较为常用)
page_text=response.text
soup = BeautifulSoup(page_text,"lxml")
BeautifulSoup对象方法

方法属性测试:

from bs4 import BeautifulSoup

fp=open("test.html",mode="r",encoding="utf-8")
soup=BeautifulSoup(fp,"lxml")

#soup.tagName类
soup.a #result:
"""
<a href="https://www.baidu.com/s?wd=迪丽热巴" target="_self" title="迪丽热巴">
<span>this is a beautiful girl</span>
            代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
</a>
"""
soup.div#result:
"""
<div>
<p>李一桐</p>
</div>

"""
#也就是说返回文档中第一次出现的tagName对应的标签
#另外  soup.find("tagName") 和  soup.tagName 用法相同



soup.find的属性定位(返回第一个找到的)

soup.find("div" # 标签
		  ,attrs={'class':"beautiful_girl"}# 标签属性定位
		  )
#result
"""
<div class="beautiful_girl">
<p>孙一宁</p>
<p>电气鼠</p>
<p>刘亦菲</p>
<p>迪丽热巴</p>
<a href="https://www.baidu.com/s?wd=迪丽热巴" target="_self" title="迪丽热巴">
<span>this is a beautiful girl</span>
            代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
        </a>
<img alt="" src="https://gimg2.baidu.com/image_search/"/>
</div>
"""

soup.find("div",attrs={'class':"beautiful_girl",'id':"legs"})
# 结果:
"""
<div class="beautiful_girl" id="legs">
<ul>
<li><a herf="https://www.baidu.com/s?wd=李一桐" title="xiao_li">代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩</a></li>
<li><a herf="https://www.baidu.com/s?wd=杨幂" title="xiao_li">代表作品:三生三世十里桃花、宫锁心玉、小时代</a></li>
<li><a herf="https://www.baidu.com/s?wd=杨紫" title="xiao_li">代表作品:家有儿女、战长沙、欢乐颂、香蜜沉沉烬如霜</a></li>
<li><b>i love you</b></li>
"""


soup.find_all 和 soup.findAll #返回所有满足条件的

soup.find_all("div",attrs={'class':"beautiful_girl"})
soup.findAll("div",attrs={'class':"beautiful_girl"})
#二者相同,返回一个bs4.element.ResultSet,相当于列表
result:
"""
[<div class="beautiful_girl">
 <p>孙一宁</p>
 <p>电气鼠</p>
 <p>刘亦菲</p>
 <p>迪丽热巴</p>
 <a href="https://www.baidu.com/s?wd=迪丽热巴" target="_self" title="迪丽热巴">
 <span>this is a beautiful girl</span>
             代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
         </a>
 <img alt="" src="https://gimg2.baidu.com/image_search/"/>
 </div>,


 <div class="beautiful_girl" id="legs">
 <ul>
 <li><a herf="https://www.baidu.com/s?wd=李一桐" title="xiao_li">代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩</a></li>
 <li><a herf="https://www.baidu.com/s?wd=杨幂" title="xiao_li">代表作品:三生三世十里桃花、宫锁心玉、小时代</a></li>
 <li><a herf="https://www.baidu.com/s?wd=杨紫" title="xiao_li">代表作品:家有儿女、战长沙、欢乐颂、香蜜沉沉烬如霜</a></li>
 <li><b>i love you</b></li>
 <li><i>you love me</i></li>
 <li><a herf="https://www.baidu.com/s?wd=郑爽" title="xiao_li">代表作品:一起来看流星雨、微微一笑很倾城、夏至未至</a></li>
 </ul>
 </div>]
"""

# soup.select('标签 id class ……')
#标签名不加任何修饰,类名前加‘.’,id名前加#
soup.select('.beautiful_girl')
"""
[<div class="beautiful_girl">
 <p>孙一宁</p>
 <p>电气鼠</p>
 <p>刘亦菲</p>
 <p>迪丽热巴</p>
 <a href="https://www.baidu.com/s?wd=迪丽热巴" target="_self" title="迪丽热巴">
 <span>this is a beautiful girl</span>
             代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
         </a>
 <img alt="" src="https://gimg2.baidu.com/image_search"/>
 </div>,
 
 <div class="beautiful_girl" id="legs">
 <ul>
 <li><a herf="https://www.baidu.com/s?wd=李一桐" title="xiao_li">代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩</a></li>
 <li><a herf="https://www.baidu.com/s?wd=杨幂" title="xiao_li">代表作品:三生三世十里桃花、宫锁心玉、小时代</a></li>
 <li><a herf="https://www.baidu.com/s?wd=杨紫" title="xiao_li">代表作品:家有儿女、战长沙、欢乐颂、香蜜沉沉烬如霜</a></li>
 <li><b>i love you</b></li>
 <li><i>you love me</i></li>
 <li><a herf="https://www.baidu.com/s?wd=郑爽" title="xiao_li">代表作品:一起来看流星雨、微微一笑很倾城、夏至未至</a></li>
 </ul>
 </div>]
"""

soup.select('.beautiful_girl'
			,limit=1
			# 只寻找一个(可自定义,没有那么多则输出全部)
			)

"""
[<div class="beautiful_girl">
 <p>孙一宁</p>
 <p>电气鼠</p>
 <p>刘亦菲</p>
 <p>迪丽热巴</p>
 <a href="https://www.baidu.com/s?wd=迪丽热巴" target="_self" title="迪丽热巴">
 <span>this is a beautiful girl</span>
             代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
         </a>
 <img alt="" src="https://gimg2.baidu.com/image_search/"/>
 </div>]
"""

soup.select('p',limit=10)
#结果:[<p>李一桐</p>, <p>孙一宁</p>, <p>电气鼠</p>, <p>刘亦菲</p>, <p>迪丽热巴</p>]

soup.select("body > .beautiful_girl > p")
#标签为body 下 class为beautiful_girl 下 标签为p 的所有数据
#用 > 表示的关系必须下面的一个层级
#用 一个空格 表示可以为跳过多个层级搜索
# [<p>孙一宁</p>, <p>电气鼠</p>, <p>刘亦菲</p>, <p>迪丽热巴</p>]

soup.select("body > .beautiful_girl > p")[0].text
#text会保留文本数据
#结果:'孙一宁'

soup.select("body p")
#结果:[<p>李一桐</p>, <p>孙一宁</p>, <p>电气鼠</p>, <p>刘亦菲</p>, <p>迪丽热巴</p>]


soup.a
"""
<a href="https://www.baidu.com/s?wd=迪丽热巴" target="_self" title="迪丽热巴">
<span>this is a beautiful girl</span>
            代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花
        </a>
"""
soup.a.text 和 soup.a.get_text()
"""
'\nthis is a beautiful girl\n            代表作品:阿娜尔罕,克拉恋人,三生三世十里桃花\n        '
"""
soup.a.string
# none
#说明text 和 get_text()表示可以获取某标签下所有内容
#string 只能获取该标签下的直系文本内容

soup.select("body > #legs > ul > li >a")[0]['herf']
# 结果: 'https://www.baidu.com/s?wd=李一桐'
# soup.select返回列表 列表每个元素为 bs4.element.Tag 
soup.a['href']
#结果:'https://www.baidu.com/s?wd=迪丽热巴'
三国演义文章内容爬取(bs4)
from bs4 import BeautifulSoup
import requests
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
url = "https://www.shicimingju.com/book/sanguoyanyi.html"
# 关于encode('ISO-8859-1')编码,在代码后说明 (1)
all_page = requests.get(url=url, headers=header).text.encode('ISO-8859-1')
# 转为bs4格式
soup_1 = BeautifulSoup(all_page, "lxml")
# 数据解析,后续说明  (2)
all_list = soup_1.select(".book-mulu > ul > li")
# 存储的文件
fp = open("san_guo_yan_yi.txt", mode="w+", encoding="utf-8")
for each_chapter in all_list:
    # each_chapter.a['href']获取的网址没有"https://www.shicimingju.com"  
    #each_chapter.a['href'] 后续讲解(2)
    each_url = "https://www.shicimingju.com" + each_chapter.a['href']
    # 在每一个详细内容界面读取数据
    title_1 = requests.get(each_url, headers=header).text.encode('ISO-8859-1')
    # 转为bs4格式
    soup_2 = BeautifulSoup(title_1, "lxml")

    # 数据解析,后续说明(3)(两种都可以)
    # detail_content=soup_2.find("div",attrs={'class':"chapter_content"}).text
    detail_content = soup_2.select(".chapter_content")[0].text

    #给每一节一个名字 后续讲解 (2)
    each_title = each_chapter.a.text
    #写入文件
    fp.write("\n" + each_title + ":" + detail_content + "\n\n")
    #提示单个文件的完成
    print(each_title, "over")
fp.close()

(1). 对于该句代码:

我们一般在读取中文文本时,经常会出现乱码的情况,有时候会是utf-8,
有时候会是ISO-8859-1,在我们后续的选择上,可以根据情况而定,
下面附上错误的乱码情况。
encode('ISO-8859-1')

如果没有这句话

乱码

(2):

bs4定位(绿色):
all_list = soup_1.select(".book-mulu > ul > li")
网址定位
each_chapter.a['href']
文本筛选  (第一回·宴桃园豪杰三结义  斩黄巾英雄首立功)
each_chapter.a.text
在这里插入图片描述

result:

在这里插入图片描述

注:all_list 和 soup_2.select 是列表 ,内部元素才可以以 [“键”] 的形式读取

xpath模块

xpath预备知识
xpath数据解析原理
  1. 实例化etree对象,并将数据源码加载进对象中
  2. 通过调用etree中xpath方法,将相关的属性和方法进行标签定位和数据提取
etree和xpath的认识和使用
实例化
#两种导库方式
from lxml import etree 
from lxml.html import etree

1. 讲本地html文档加载进该对象中
res=etree.parse('./test.html', etree.HTMLParser())#不需要用ope打开
#对于etree.HTMLParser(),暂时不明白原因,期待大家帮助
#返回<lxml.etree._ElementTree at 0x1fb534d6440>

2. 将互联网上的页面源码加载到对象中(较为常用)
response=requests.get(url,headers=header).text
res=etree.HTML(response)#response必须为text
#返回<Element html at 0x1fb531afa80>

导入结果是这样的,可能是pycharm虚晃一枪

在这里插入图片描述
实例化后xpath表达式的使用
"""
<html lang="aa">
<head>
    <meta charset="UTF-8">
    <title>测试bs4</title>
</head>
(全部请看test.html文件)
"""

from lxml import etree
res = etree.parse('./test.html', etree.HTMLParser())

res.xpath("/html/head/title")
res.xpath("head/title")
res.xpath("//head/title")
res.xpath("//title")
res.xpath("/html//title")
res.xpath("//html//title")
#均返回 [<Element title at 0x2dd83efb900>]
res.xpath("html/head/title")
res.xpath("/head/title")
#返回空列表
#说明第一个标签前是否有单个 / 需要根据是否为根标签定,// 任意放于最前面
# // 表示下一个或者多个层级


那么如何在根据class id title  定位呢?
res.xpath("/html/body/div[@class='beautiful_girl'][@id='legs']")
这就是定位到了body中class为'beautiful_girl'且id为'legs'的div
就是 标签名[@属性名="属性名对应的内容"]

那么接下来进行索引定位
!!!注:xpath索引是从1开始,不是0
直接举例子:取出李一桐的代表作
"""
    <div class="beautiful_girl" id="legs">
        <ul>
            <li><a herf="https://www.baidu.com/s?wd=李一桐" title="xiao_li">代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩</a></li>
"""
res.xpath("//div[@class='beautiful_girl'][@id='legs']/ul/li[1]/a/text()")
返回列表:['代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩']
#/test()将Element转为文本
res.xpath("//div[@class='beautiful_girl'][@id='legs']/ul/li[1]/a/text()")[0]
直接获取字符串内容


/text()和//text将Element转为文本的区别
res.xpath("/html/body/div[@class='beautiful_girl'][@id='legs']/text()")
结果:['\r        ', '\r    ']
#仅仅将div下ul之间的换行文本读取了,不可跨标签等级
res.xpath("/html/body/div[@class='beautiful_girl'][@id='legs']//text()")
#将div下所有存在的文本读取了,可跨标签等级
结果: 
['\r        ',
 '\r            ',
 '代表作品:射雕英雄传,鹤唳华亭,媚者无疆,了不起的女孩',
 '\r            ',
 '代表作品:三生三世十里桃花、宫锁心玉、小时代',
 '\r            ',
 '代表作品:家有儿女、战长沙、欢乐颂、香蜜沉沉烬如霜',
 '\r            ',
 'i love you',
 '\r            ',
 'you love me',
 '\r            ',
 '代表作品:一起来看流星雨、微微一笑很倾城、夏至未至',
 '\r        ',
 '\r    ']


有些网址就在属性里,那么怎么取属性呢?
观察这句代码:
res.xpath("//div[@class='beautiful_girl'][@id='legs']/ul/li[1]/a/@herf")
结果:['https://www.baidu.com/s?wd=李一桐']
即:在对应标签后加上 /@属性名 即可获得
res.xpath("//div[@class='beautiful_girl'][@id='legs']/ul/li/a/@herf")
结果:
['https://www.baidu.com/s?wd=李一桐',
 'https://www.baidu.com/s?wd=杨幂',
 'https://www.baidu.com/s?wd=杨紫',
 'https://www.baidu.com/s?wd=郑爽']
 
一次获取多个
res.xpath("//div[@class='beautiful_girl'][@id='legs']/ul/li[1]/a/@herf | //div[@class='beautiful_girl'][@id='legs']/ul/li[2]/a/@herf")

温馨提示:一定要注意列表还是字符串,要注意转换
二手房名称信息获取(xpath)
代码实现
import requests
from lxml import etree
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
url = "https://zz.58.com/ershoufang/"
house_text=requests.get(url=url,headers=header).text
#注意一定有.text  否则报错

house_tree=etree.HTML(house_text)#实例化
res=house_tree.xpath("//section[@class='list']/div[@class='property']/a//h3/text()")
#代码后讲解xpath表达式 先看结果:
"""
['Ai科技社区 首付一万 带全智能机器人 全套家具 看房接送'
,………………
, '(新)!惠济区 外国语中学  连霍高速 贾鲁河畔旁']
"""

print(res)
fp=open("test.txt",mode="w+",encoding="utf-8")
for i in res:
    fp.write(i+'\n')
    fp.write('\n')
fp.close()

result:

jieguo1
讲解xpath:

在这里我们逐步展开:

58
res_1=house_tree.xpath("//section[@class='list']/div[@class='property']")
print(res_1)
结果:
[<Element div at 0x2227ee1fd00>, …………,<Element div at 0x2227ea6ca00>]

res_2=house_tree.xpath("//section[@class='list']/div[@class='property']//text()")
print(res_2)
结果:
[' ', ' ', ' ', 'Ai科技社区 首付一万 带全智能机器人 全套家具 看房接送', ' ', '  ', '广告', ' ', '3', ' ', '室', ' ', '2', ' ', '厅',……………………]
!!!我们发现结果就在该目录下
(我想这个就比较好理解)

解析重点来了:/a/h3怎么确定的呢

沙发上
这么多代码,都看不全,怎么办?
对了
就用之前提到的json工具
来让我们看看吧!!!
(我们以第一个为例)
还
看到没,文字在标签h3title和文本出现,但是h3我们不知道是不是直系于a
所以用//更好,那么就有了两种写法
house_tree.xpath("//section[@class='list']/div[@class='property']/a//h3/@title")
house_tree.xpath("//section[@class='list']/div[@class='property']//text()")
均返回列表
遗留问题
是不是感觉这样就结束了:
细心的伙伴可能发现我们网站上第一个是
"东城半岛。简装3室,电梯双气,2楼"
在test.txt文件结果中却是
"Ai科技社区 首付一万 带全智能机器人 全套家具 看房接送"
但是后面也存在
"东城半岛。简装3室,电梯双气,2楼"
说明在解析过程中将顺序改变了
但是up猪搜索了很多也没找到原因,希望知道的小伙伴私信我啊!
关于图片(你懂的)
代码实现
import os
import requests
from lxml import etree
header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}

url = "https://pic.netbian.com/4kmeinv/"

girl_data=requests.get(url=url,headers=header).text
# 处理乱码  .decode('gbk')  可去掉
girl_data=girl_data.encode("iso-8859-1").decode('gbk')
#实例化模型
girl_etree=etree.HTML(girl_data)

#xpath表达式代码后说明
picture_loc=girl_etree.xpath("//ul[@class='clearfix']/li/a/img/@src")
picture_name_list=girl_etree.xpath("//ul[@class='clearfix']/li/a/img/@alt")

#新增文件夹
if not os.path.exists("you_knew_about_picture"):
    os.mkdir("./you_knew_about_picture")

#增加索引定 picture_name_list
for i,each_loc in enumerate(picture_loc):
	#完整网址
    new_loc="https://pic.netbian.com/"+each_loc
    #爬取图片    
    each_picture_data=requests.get(new_loc,headers=header).content
    #each_picture_name由文件路径和名组成
    each_picture_name="you_knew_about_picture/"+picture_name_list[i]+".jpg"
    #打开文件
    fp=open(each_picture_name,mode="wb")
    #写入
    fp.write(each_picture_data)
    fp.close()
    #提示完成
    print(each_picture_name.split("/")[-1]+" have been over")

讲解xpath:
注
实际上:
picture_name_list=girl_etree.xpath("//ul[@class='clearfix']/li/a/img/@alt"
也可以用:
picture_name_list=girl_etree.xpath("//ul[@class='clearfix']/li/a/b/text()"
替换

嘿嘿嘿!!!我懂

在这里插入图片描述
关于更多图片(dddd)
import os
import requests
from lxml import etree

header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}

picture_loc = []  # 图片存地址
picture_name_list = []  # 存图片名
# 第2,3页图片,可自行调节
# 不能包括1,因为1页面网址和后面网址不一样,如想包括,可添加if条件判断
for i in range(2, 4):
    # 一定要在循环内,否则一直为"https://pic.netbian.com/4kmeinv/index_2.html"
    # 关于为什么后面是/4kmeinv/index_{0}.html 代码后讲解
    url = "https://pic.netbian.com/4kmeinv/index_{0}.html"
    url = url.format(i)
    girl_data = requests.get(url=url, headers=header).text
    girl_data = girl_data.encode("iso-8859-1").decode('gbk')
    girl_etree = etree.HTML(girl_data, )
    # 地址压入
    picture_loc.extend(girl_etree.xpath("//ul[@class='clearfix']/li/a/img/@src"))
    # 图片名压入
    picture_name_list.extend(girl_etree.xpath("//ul[@class='clearfix']/li/a/b/text()"))

if not os.path.exists("you_knew_about_picture"):
    os.mkdir("./you_knew_about_picture")

a = 0  # 记录图片个数
for i, each_loc in enumerate(picture_loc):
    new_loc = "https://pic.netbian.com/" + each_loc

    each_picture_data = requests.get(new_loc, headers=header).content

    each_picture_name = "you_knew_about_picture/" + str(a) + " . " + picture_name_list[i] + ".jpg"

    fp = open(each_picture_name, mode="wb")
    fp.write(each_picture_data)
    fp.close()

    print(each_picture_name.split("/")[-1] + " have been over")
    a = a + 1
print(a)
网址解析

观察第2,3,界面网址:

在这里插入图片描述
在这里插入图片描述

(我想你已经懂了)

但是:

https://pic.netbian.com/4kmeinv/index_1.html
不是第一页
只能是
https://pic.netbian.com/4kmeinv/

结果

在这里插入图片描述
在这里插入图片描述
文档下载案例
代码实现
import os
import requests
from lxml import etree

header = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 SLBrowser/7.0.0.5211 SLBChan/25"
}
url="https://sc.chinaz.com/ppt/?classid=&keyword=免费"

data_1=requests.get(url=url,headers=header).text
data_tree=etree.HTML(data_1)
# 地址和名称读取
data_loc_list=data_tree.xpath("//div[@class='bot-div']/a/@href")
data_name_list=data_tree.xpath("//div[@class='bot-div']/a/@title")
#第一个文档付费所以去掉
data_loc_list.pop(0)
data_name_list.pop(0)

if not os.path.exists("./ppt_muban"):
    os.mkdir("./ppt_muban")

for i,each_loc in enumerate(data_loc_list):
    #完整化网址
    loc="http:"+each_loc
    #进入详情页
    data_2=requests.get(url=loc,headers=header).text.encode("iso-8859-1").decode('utf-8')
    #获取文档下载地址
    each_download_loc=etree.HTML(data_2).xpath("//div[@class='download-url']/a[2]/@href")[0]
    # 下载文档
    each_data=requests.get(url=each_download_loc,headers=header).content

    #存储
    fp=open("./ppt_muban/"+data_name_list[i],mode="wb")
    fp.write(each_data)
    fp.close()
    #完成提示
    print(data_name_list[i]+" has been over")
print("all have been over")

结果

在这里插入图片描述
阿发

详情页:

在这里插入图片描述
实现时可能存在问题
1. 文字乱码
.encode("iso-8859-1").decode('gbk')
.encode("iso-8859-1").decode('utf-8')
.encode("iso-8859-1")
.encode("utf-8")
都可尝试一下

2. 关于特殊情况的发现:

第一个文档付费所以去掉
data_loc_list.pop(0)
data_name_list.pop(0)

在运行时关于这句代码显示‘list index out of range’,说明正则化出现问题
就去检查xpath表达式,结果发现没问题,通过调试搜过到导致错误的网址后打开发现是付费的。
each_download_loc=etree.HTML(data_2).xpath("//div[@class='download-url']/a[2]/@href")[0]
!!!也不要忘了[0]啊

3. 还有就是文件格式打开,rar问价是二进制文件,"wb"方式打开

4. 爬取的时候不要忘了等待一下,因为文件比较多,跟网速有一定关系。
 
5. 关于xpath表达式返回空列表:
除了正则表达式错误外,我们也可以检查一下在运行过程中对数据读取的情况是否和我们页面上内容一致

举个栗子:
data_1=requests.get(url=url,headers=header).text
data_tree=etree.HTML(data_1)
data_loc_list=data_tree.xpath("//div[@class='bot-div']/a/@href")
若data_loc_list空,则我们看看data_1在pycharm中显示的内容是否符合我们的预期就像下图一样
啊师傅
处理数据懒加载
什么事懒加载

懒加载(Load On Demand)是一种独特而又强大的数据获取方法,它能够在用户滚动页面的时候自动获取更多的数据,而新得到的数据不会影响原有数据的显示,同时最大程度上减少服务器端的资源耗用。

  • 反爬机制懒加载广泛应用在一些图片网站中,由于懒加载的产生,对于我们的爬虫有所影响,所以我们需要应对这种方式。
  • 只有当图片被显示在浏览器可视化范围之内时才会将伪属性变为真正的属性,如果是用爬虫requests请求,那么请求是没有可视化范围的,因此我们要解析的是伪属性的属性值。

让我们现在用图片了解一下,懒加载到底是什么

直接举例子

图片懒加载网址

当我们刚打开网址的时候并没有进行下滑,我们看得到本页面图片的网址,在属性为src的属性的值里面。

在这里插入图片描述

但是在我们不下滑页面,但是搜索后面图片的代码时会发现,网址并没有在属性为src的属性值里面,而是在属性为src2的属性值里面。那么这个src就是真正的属性,src2就是我们所说的伪属性,但是在我们request中是没有可视化范围,只能请求到src2属性,所以我们要对于src2中的数据进行解析

在这里插入图片描述
就相当于在xpath解析式中
"//a[@target='_blank']/img/@src"
改为
"//a[@target='_blank']/img/@src2"
其他均不改变

对于该机制有时候不容易被注意到
所以建议在爬取图片的时候优先注意这一点--是否为懒加载
本章结束

数据位置寻找方法和xpath快速定位

  1. 点击箭头后点击想要的内容,就会自动跳转
    在这里插入图片描述
  2. 在Network中搜索
    在这里插入图片描述
  3. 在包里面搜索
    在这里插入图片描述
  4. 快速生成xpath表达式
    在这里插入图片描述
    在这里插入图片描述
    细不细惊呆了
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门