用BeautifulSoup库解析和提取数据
解析特定标签的网页元素
我最初喜欢用正则表达式来进行爬取,后来发现利用标签的形式解析源代码十分方便,也就是直接用开发者工具中的内容进行分析。
from bs4 import BeautifulSoup
# 固定写法导入BeautifulSoup库
soup = BeautifulSoup(res, 'html.parser')
# ‘html.parser’表示设置解析器为HTML解析器
# res是之前创建的需要解析的网页源码
# 并将解析器命名为soup
title = soup.select('h1')
# 选取所有<h1>标签,传入的参数为标签名
print(title)
# 输出传出的标签名,也就是两个h1标签中间的内容
因为传出的标签名可能有其他一些多余的成分,所以如果想进一步提取标签中的文本内容,需要遍历各个标签,然后通过text属性提取文本内容,代码如下:
for i in title:
print(i.text)
解析特定属性的网页元素
由于有些标签具有特定的属性,例如class和id。
例如所有标签的clss属性都是“title”,那如下方法解析:
title = soup.select('.title')
print(title)
同前一个部分不同,这里在select函数中输入的参数不是标签名,而是class属性的值,并且在属性值前面都要加上“.”,用于声明寻找的是class属性。会把class属性为“title”的标签全部选取出来。
注:如果寻找的是id属性,那需要在属性值前面加上“#”号来声明寻找的是id属性。
select()函数的多层次筛选
由于网页源代码中的标签通常是层层嵌套的关系,所以可以使用多层次筛选功能:
from bs4 import BeautifulSoup
res = '''
<div class = "result1">
<h1 class = "title">南京中医药大学</h1>
</div>
<div class = "result2">
<h1 class = "title">中药213</h1>
</div>'''
# 临时代码,<div>标签中还有<h1>标签
soup = BeautifulSoup(res, 'html.parser')
title = soup.select('.result1 h1')
# 多层次筛选class属性为result1中标签为h1的信息
# 也可写成title = soup.select('.result1 .title')
print(title)
利用XPath定位网页元素的具体方法
查找与定位XPath表达式
我是遇到一个问题,例如这段代码中文字在两个便签之外,利用strong标签定位定位不到(在它们之外),利用p标签的话定位到的内容太多了,不方便后续数据清洗。所以学习一下Xpath方法来定位标签。
(注:如果输出XPath的话,输出的是一个类似于编码的信息)
Xpath可以理解成网页元素的名字或者id,可以利用一下语句格式根据XPath表达式来定位网页元素
find_element_xpath('XPath表达式')
利用开发者工具可以获取网页元素的XPath表达式,如下图所示,按【F12】键打开开发者工具,1.单击左上角的元素选择工具按钮;2.在网页中随机选中希望知道其XPath表达式的内容,例如我在这边选中了右上角的搜索框;3.右键选中内容对应的源码,依次选择Copy-Copy XPath命令,即可把搜索框的XPath表达式复制到剪贴板,在编写代码时就可以粘贴到函数中作为参数。
在这个例子中我们最终得到如下代码:
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('http://www.cnkang.com/tags/15232.html')
browser.find_element_by_xpath('//*[@id="bdcsMain"]').send_keys('薄荷')
browser.find_element_by_xpath('/html/body/div[1]/div/div/form/ul/input[2]').click()
其中第四行意指定位到搜索框上,.send_key(‘薄荷’),意识是在搜索框中输入薄荷;第五行是搜索键的XPath,.click()意指点击该键。
所以整段代码的意识是先定位到搜索框,然后定位到搜索键上并点击,最后得到的结果如下:
XPath表达式的高级用法
我看的是这位李富贵要上岸985同学的文章,受益匪浅,现在在他的基础上,加上我自己的理解分享给大家
https://blog.csdn.net/weixin_45755332/article/details/107193013
这位同学的文章中列出了几种表达式与对应用法,我在这边用具体案例来进行实操表达,以如下文档为例(来源于该同学):
定位子、子孙节点(/,//)
观察网页源码可知,其结构关系为html - body - div(共有三个分区,分别为“百里守约”,“song”,“tang”)。
import requests
from lxml import etree
tree = html.etree.parse('test.html') # 此处代码不能运行,原因未知
r1=tree.xpath('/html/body/div') #直接从上往下挨着找节点
r2=tree.xpath('/html//div')#跳跃了一个节点来找到这个div节点的对象
r3=tree.xpath('//div')##跳跃上面所有节点来寻找div节点的对象
这两个的用法是按照逻辑结构次序来进行遍历后输出。
定位分区中的标签(.path)
和我上面所述查找与定位XPath表达式的内容差不多,我在这边发现一个有趣的东西,例如:
r4=tree.xpath('//div[@class="song"]')
r4
>>
>[<Element div at 0x19d447658c8>]
它这边@class后面的内容其实就是它的区名。
对分区的标签索引(/p, /p[])
续上,我们在获得class为“song”的类以后,可以在最后加上/p来返回所有的标签
tree.xpath('//div[@class="song"]/p')
如果想要具体返回某一个标签,那就观察其对应的标签序号,并且注意,这里面从1开始,而非代码中常认为的0。
tree.xpath('//div[@class="song"]/p[3]')
取标签中分文本()
同样以取“杜牧”这个标签为例
首先观察“杜牧”所在的位置,在第三个div(class = tang),的第五个li标签中。
所以有如下写法:
tree.xpath('//div[@class="tang"]//li[5]/a/text()')
>>
['杜牧']
@class = “tang”定位了“杜牧”这个标签所在的分区,“//li[5]/a”表示这是第五个li标签,中的a标签,最后的text()函数用于将element格式的内容转换成文本。
在我的理解里面,这里面跳过了一个ul标签,所以采用“//”,所以也可以写成如下形式:
tree.xpath('//div[@class="tang"]/ui/li[5]/a/text()')
>>
['杜牧']
爬取中华康网清热药信息的操作步骤
1. 爬取中华康网中清热药网页源码
这个在我之前的的爬虫基础知识分享中有提过,所以直接给出源码。
注:我在看其他人的代码时发现,很多人会用多个函数的形式,将爬虫代码分成爬取源码、解析等步骤,这样看上去更清楚一些,所以在此学习一下。
# 1. 获取网页源码并转换成文本格式
def getcon():
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'}
url = "http://www.cnkang.com/cm/zcy/jry/" # 中华康网上清热药的网址
res = requests.get(url, headers=headers) # 使用requests.get()函数访问该网址
res.encoding = 'utf-8' # 使用'utf-8'的编码格式来重新编码及解码
res = res.text
analyse(res)
2. 解析网页源代码并提取网址数据
观察网页源码可知,每种植物与它对应的网址在class属性值为catalog04的标签下,具体是dd中的a标签。
然后输出网址前加上爬取到的网址缺失的前缀,这边我用了replace()函数,把空格部分替代成了不空格。
# 2. 解析网页源码
def analyse(res):
soup = BeautifulSoup(res, 'html.parser')
med = soup.select('.catalog04 dd a')
# 观察源码中带有每种植物与对应网址的在class为catalog04下,<dd>中的<a>标签
for i in range(len(med)):
print(str(i + 1) + '.' + med[i].text)
temp = ('http://www.cnkang.com/' + str(med[i]['href']))
print(temp.replace(" ", ""))
# 分别输出一种植物加上它对应的网址
此时输出结果为一种植物的序号与它的名称,另起一行为它的网址
我为了后续还要继续访问网址,对其进行操作,所以尝试将网址与名称分别保存在两个列表里。
# 2. 解析网页源码
def analyse(res):
soup = BeautifulSoup(res, 'html.parser')
med = soup.select('.catalog04 dd a')
# 观察源码中带有每种植物与对应网址的在class为catalog04下,<dd>中的<a>标签
med_name = []
med_url = []
# 创建两个存放名称和网址的列表
for i in range(len(med)):
temp = ('http://www.cnkang.com/' + str(med[i]['href']))
med_name.append(med[i].text)
med_url.append(temp.replace(" ", ""))
# 将对应的名称和网址加入对应的列表中
detailed_information(med_name, med_url)
3. 各味清热药的别名,药性等信息
最初我的想法是用正则表达式,但是繁杂无用数据过多,效率实在是很低,在此附上代码:
def crawler(res):
h_res = '<strong>.*?<>(.*?)<p>'
result = re.findall(h_res, res)
print(result)
所以后来我采用XPath的提取方法,实践是检验真理的唯一标准,我发现在“李富贵要上岸985”同学的代码从文本文档到开始利用XPath方法进行定位前那一段,然后我找到了解决方法,问题原因我暂时仍然不清楚,先保留一下。
import requests
from lxml import html
# 附3. 用来在源码中爬取具体信息
def crawler(res):
tree = html.etree.HTML(res.content, html.etree.HTMLParser(encoding = 'utf-8'))
Introduction = tree.xpath('//div[@class = "zh05b"]//p/text()')
print(Introduction)
if __name__=="__main__":
url = "http://www.cnkang.com/tags/15232.html" # 中华康网上清热药的网址
res = requests.get(url)
crawler(res)
并且他没有采用head的方式来解析获得网页源码,我猜测是因为传递数据用的是一种编译后的格式,不像之前需要转换成为文本格式再调整,所以不用head这个多余的步骤
# 附3. 用来在源码中爬取具体信息
def crawler(res):
tree = html.etree.HTML(res.content, html.etree.HTMLParser(encoding = 'utf-8'))
temp_re = tree.xpath('//div[@class = "zh05b"]//p/text()')
print(temp_re)
完整代码
import requests
import re
from lxml import html
from bs4 import BeautifulSoup
from urllib.request import urlretrieve
# 1. 获取网页源码并转换成文本格式
def getcon():
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'}
url = "http://www.cnkang.com/cm/zcy/jry/" # 中华康网上清热药的网址
res = requests.get(url, headers=headers) # 使用requests.get()函数访问该网址
res.encoding = 'utf-8' # 使用'utf-8'的编码格式来重新编码及解码
res = res.text
analyse(res)
# 2. 解析网页源码
def analyse(res):
soup = BeautifulSoup(res, 'html.parser')
med = soup.select('.catalog04 dd a')
# 观察源码中带有每种植物与对应网址的在class为catalog04下,<dd>中的<a>标签
med_name = []
med_url = []
# 创建两个存放名称和网址的列表
for i in range(len(med)):
temp = ('http://www.cnkang.com/' + str(med[i]['href']))
med_name.append(med[i].text)
med_url.append(temp.replace(" ", ""))
# 将对应的名称和网址加入对应的列表中
detailed_information(med_name, med_url)
# 3. 爬取各味清热药的别名,药性等信息
def detailed_information(name, url):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36'}
Introduction = [] # 用来存储简介
medicinal = [] # 用来存储药性
efficacy = [] # 用来存储功效与作用
indications = [] # 用来存储适应症
for i in range(len(url)):
temp_url = url[i]
temp_res = requests.get(temp_url)
crawler(temp_res)
# 附3. 用来在源码中爬取具体信息
def crawler(res):
tree = html.etree.HTML(res.content, html.etree.HTMLParser(encoding = 'utf-8'))
temp_re = tree.xpath('//div[@class = "zh05b"]//p/text()')
print(temp_re)
if __name__ == "__main__":
getcon()
不足之处
主要是在最后数据的清洗部分,我没有进行细致的清洗与排布,只是简单的输出需要的数据,等到后面我还要系统学习一下更加整齐的排布方法。