0


【爬虫】DrissionPage库的一次实战练习记录

前言

背景:项目需要爬取来自MCE制药公司网页展示的药物数据

网页示例:Mitophagy激活剂、基因 | MCE

难点:数据不太好取+我懒得复习request+beautifulSoup

需求分析

遍历下图展示的网页中超链接对应的具体产品页面

如图共6页,每页约展示20个产物(紫色字体包含超链接)

点击紫色字体后会打开新标签页,如下图所示

真正需要采集的是其中的药物学名、描述和图片等数据

框架

比beautifulSoup更好用的DrissionPage库,你值得拥有

DrissionPage 是一个基于 python 的网页自动化工具。

它既能控制浏览器,也能收发数据包,还能把两者合而为一。

可兼顾浏览器自动化的便利性和 requests 的高效率。

它功能强大,内置无数人性化设计和便捷功能。

它的语法简洁而优雅,代码量少,对新手友好。

👆来自DrissionPage官网,不是我写的,但我完全认同

DrissionPage中定义了三种对象(第三种对象实际上为前两种的结合,故不作介绍):

SessionPage

实现收发数据包,即不需要控制浏览器的情况下,调用此对象的方法可以快速的抓取网站数据

如果每个药物网页的链接有规律可循,则可使用此对象,不操控浏览器就能抓取数据

然而,该网站中的所有药物网页均以学名来命名,没有规律或区间,如下图

每个药物的页面只能通过图一这样的列表展示页来逐一访问,故使用下方的ChromiumPage对象

ChromiumPage

该对象实现对浏览器的控制,并且可以非常方便的控制网站中的标签页。

元素定位和获取

上述两种对象都可以通过相对简单的语法规则定位并获取HTML元素。

如匹配HTML中的id标签,只需要"#"符号

# 在页面中查找id属性为one的元素
ele1 = page.ele('#one')

# 在ele1元素内查找id属性包含ne文本的元素
ele2 = ele1.ele('#:ne')  

而匹配HTML中的class标签则是'.'符号

# 查找class属性为p_cls的元素
ele2 = ele1.ele('.p_cls')

# 查找class属性'_cls'文本开头的元素
ele2 = ele1.ele('.^_cls')  

对于其他标签,可以通过'tag'进行匹配,同时可以采用'@'符号匹配属性,或通过'@@'符号匹配多种属性(逻辑为'与',类似&&)

# 定位div元素
ele2 = ele1.ele('tag:div')

# 定位class属性为p_cls的p元素
ele2 = ele1.ele('tag:p@class=p_cls')

# 定位文本为"第二行"的p元素
ele2 = ele1.ele('tag:p@text()=第二行')

# 查找name属性为row1且class属性包含cls文本的元素
ele2 = ele1.ele('@@name=row1@@class:cls')

在取得了某项元素后,该框架也提供了便捷的属性访问方法。

例如对于HTML代码

<div id="div1" class="divs">Hello World!
    <p>行元素</p>
</div>

通过.text可以返回元素内所有文本组合成的字符串(不包含任何标签内的文字)

print(ele.text)

运行结果为

Hello World!
行元素

而对于含有链接的HTML代码如

<a href='http://www.baidu.com'>百度</a>

使用.link可以获取其中的链接,包括href和src

print(a_ele.link)

运行结果为

http://www.baidu.com

更多方法和参数请查阅DrissionPage官网

实战

在快速学习过上述基础后,就可以进入实战,主要放上代码和注释

首先正常导入库,并设定参数

考虑到部分药物网页可能缺少某个元素,提前设置好填充值

# 导入必要的库
from DrissionPage import ChromiumPage
from DrissionPage.common import Settings

# 创建页面对象
page = ChromiumPage()
# 如果元素找不到怎么办?先设置不报错
Settings.raise_when_ele_not_found = False
# 然后设置一个默认值进行填充
page.set.NoneElement_value('')

使用 page.get,打开图1中的列表展示页面,这里仅展示爬取第一页的内容

page.get(f'https://www.medchemexpress.cn/Targets/Mitophagy/effect/activator.html?page=1')

若需要爬取多页,则使用循环并修改page即可

for i in range(1, n):
    page.get(f'https://www.example.com?page={i}')

接下来开始定位元素,使用浏览器的审查元素功能

可以看出所有药物网页均被包裹在如上图所示的'id=sub_ctg_list_target'中

而每个具体的网页链接则被包裹在标签内

使用框架的ele方法(返回整个块,然后对块使用eles方法(返回块内所有标签组成的列表)

然后对获取到的药物列表进行遍历

 # 获取产品列表
    list = page.ele('#sub_ctg_list_target')
    links = list.eles('tag:a')
    for link in links:

然而这种采集方法会采集到一些额外的超链接,如下图所示详情页面中有时会介绍药物对应的受体,而受体同样包含超链接

同样由上图右侧,可以注意到所有受体对应的链接均位于网站的'/Targets'目录下,故采用框架提供的文本匹配方法来筛选掉受体超链接,和网页中用于阻止浏览器默认超链接的'javascript:(0);'

然后使用tab对象操作标签页,new_tab()方法建立新标签页,get方法使标签页转向我们刚刚获取并初步筛选过的超链接。link.link即获取link对象

        if link.link != 'javascript:(0);' and ('Targets' not in link.link):
            tab = page.new_tab()
            tab.get(link.link)

同时注意到列表中的部分药物除英文名外还有中文名称,并且中文名称包含重复的超链接

为了防止重复打开标签页和采集,观察两种名称的HTML元素

可以发现两种名称的父元素不同,因此使用框架中的parent方法进行二次筛选

# 判断link的父元素,如果是th说明是英文对应超链接,p则为中文超链接。使用.tag获取
            tag = link.parent(1).tag
            # 部分药剂有中文和英文名字,两个名字都包含相同超链接,造成重复采集;
            # 只选取英文超链接
            if tag == 'th':

接下来就开始正式定位和采集了

观察具体药物的网页,以几张不同的元素为例

简单的文本元素

上图是药物名称的HTML代码,可以看到name元素被包裹在<id>中,使用'#'符号匹配即可

# .text方法获取内部文本
name = tab.ele('#head_pro_name').text

表格包裹的元素

这种稍微麻烦一些

可以看到左侧为包裹的表头,右侧为包裹的表格内容,而我们需要采集的就是右侧内容

# 先搜索tr结构,然后搜索指定的td列
formula = tab.ele('tag:tr@@text():生物活性').ele('.details_info_td').text

代码有点长,其实就是双重ele,首先使用'tag'匹配该HTML页面中的所有,即表格标签,同时使用'@@'即多属性匹配('与'逻辑),筛选出包含'生物活性'文本的那个表格。

然后使用第二次ele,从标签内部再通过'.'符号匹配class属性为'details_info_td'的表内容,最后使用text方法获取其中的纯文本信息。

图片元素

项目还需要采集网页中的图片,并且将图片也爬取到本地保存。我们分两步来,获取图片,控制浏览器下载图片。

观察HTML代码可知图片的链接被包裹在<id>中,使用如下代码匹配

image_source = tab.ele('#pro_structure_img')

但匹配得到的其实是整个标签,而我们需要的只是其中的src部分。故使用框架提供的attr()方法获取src,并且通过此方法获取的src是包括网站前缀的完整链接,不需要手动补全

url = image_source.attr('src')

获取图片url后,框架也提供了便捷的下载方法,可以控制标签页对象使用浏览器的下载方式。这里注意到网页提供的图片都是.gif后缀,并且命名不统一,我们可以手动去修改这些参数

# 定义保存路径
savepath = r'E:\exampleImage'
# 调用标签页对象的下载方法
# 修改后缀名,并重命名文件
res = tab.download(url, savepath, suffix='png', rename=f'{flag}.' + name)

数据保存和网站协议

数据的存储使用pandas提供的DataFrame对象即可,图片使用WPS一件插入即可

 raw_data = {'Name': name, 'Formula': formula...etc}
 df = pd.concat([df, pd.DataFrame([raw_data])], ignore_index=True)

网站带有爬虫协议,本项目认真阅读并遵守了协议内容。

后记

使用该框架大大提高了本项目爬虫部分的效率,并且相当简单易用。但还有一些值得改进和思考的地方,例如缺少某项数据,需要使用预填充值的网页,其爬取速度要远低于数据齐全的网页。具体来说,数据齐全网页打开后2~3秒左右自动关闭,而数据缺少网页需要至少10秒时间。单纯的数据填充不应该产生这么大的影响,还需要继续学习框架寻找原因。


本文转载自: https://blog.csdn.net/weixin_41477468/article/details/137524530
版权归原作者 群山行云: 所有, 如有侵权,请联系我们删除。

“【爬虫】DrissionPage库的一次实战练习记录”的评论:

还没有评论