1. 如何在python中使用xpath
在进行网页抓取的时候,分析定位html节点是获取抓取信息的关键,目前我用的是lxml模块(用来分析XML文档结构的,当然也能分析html结构), 利用其lxml.html的xpath对html进行分析,获取抓取信息。
首先,我们需要安装一个支持xpath的python库。目前在libxml2的网站上被推荐的python binding是lxml,也有beautifulsoup,不嫌麻烦的话还可以自己用正则表达式去构建,本文以lxml为例讲解。
假设有如下的HTML文档:
1 <html>
2 <body>
3 <form>
4 <div id='leftmenu'>
5 <h3>text</h3>
6 <ul id=’china’><!-- first location -->
7 <li>...</li>
8 <li>...</li>
9 ......
10 </ul>
11 <ul id=’england’><!-- second location-->
12 <li>...</li>
13 <li>...</li>
14 ......
15 </ul>
16 </div>
17 </form>
18 </body>
19 </html>
直接使用lxml处理:
1 import codecs
2 from lxml import etree
3 f=codecs.open("ceshi.html","r","utf-8")
4 content=f.read()
5 f.close()
6 tree=etree.HTML(content)
etree提供了HTML这个解析函数,现在我们可以直接对HTML使用xpath了,是不是有点小激动,现在就尝试下吧。
在使用xpath之前我们先来看看作为对照的jQuery和RE。
在jQuery里要处理这种东西就很简单,特别是假如那个ul节点有id的话(比如是<ul id=’china’>):
$("#china").each(function(){...});
具体到此处是:
$("#leftmenu").children("h3:contains('text')").next("ul").each(function(){...});
找到id为leftmenu的节点,在其下找到一个内容包含为”text”的h3节点,再取其接下来的一个ul节点。
在python里要是用RE来处理就略麻烦一些:
block_pattern=re.compile(u"<h3>档案</h3>(.*?)<h3>", re.I | re.S)
m=block_pattern.findall(content)
item_pattern=re.compile(u"<li>(.*?)</li>", re.I | re.S)
items=item_pattern.findall(m[0])
for i in items:
print i
那么用xpath要怎么做呢?其实跟jQuery是差不多的:
nodes=tree.xpath("/descendant::ul[@id='china']")
当然,现在没有id的话也就只能用类似于jQuery的方法了。完整的xpath应该是这样写的(注意,原文件中的TAG有大小写的情况,但是在XPATH里只能用小写):
nodes=tree.xpath(u"/html/body/form/div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")
更简单的方法就是像jQuery那样直接根据id定位:
nodes=tree.xpath(u"//div[@id='leftmenu']/h3[text()='text']/following-sibling::ul[1]")
这两种方法返回的结果中,nodes[0]就是那个“text”的h3节点后面紧跟的第一个ul节点,这样就可以列出后面所有的ul节点内容了。
如果ul节点下面还有其他的节点,我们要找到更深节点的内容,如下的循环就是把这些节点的文本内容列出:
nodes=nodes[0].xpath("li/a")
for n in nodes:
print n.text
对比三种方法应该可以看出xpath和jQuery对于页面的解析都是基于XML的语义进行,而RE则纯粹是基于plain text。RE对付简单的页面是没有问题,如果页面结构复杂度较高的时候(比如一堆的DIV来回嵌套之类),设计一个恰当的RE pattern可能会远比写一个xpath要复杂。特别是目前主流的基于CSS的页面设计方式,其中大部分关键节点都会有id――对于使用jQuery的页面来说则更是如此,这时xpath相比RE就有了决定性的优势。
附录:基本XPATH语法介绍,详细请参考XPath的官方文档
XPATH基本上是用一种类似目录树的方法来描述在XML文档中的路径。比如用“/”来作为上下层级间的分隔。第一个“/”表示文档的根节点(注意,不是指文档最外层的tag节点,而是指文档本身)。比如对于一个HTML文件来说,最外层的节点应该是”/html”。
同样的,“..”和“.”分别被用来表示父节点和本节点。
XPATH返回的不一定就是唯一的节点,而是符合条件的所有节点。比如在HTML文档里使用“/html/head/scrpt”就会把head里的所有script节点都取出来。
为了缩小定位范围,往往还需要增加过滤条件。过滤的方法就是用“[”“]”把过滤条件加上。比如在HTML文档里使用“/html/body/div[@id='main']”,即可取出body里id为main的div节点。
其中@id表示属性id,类似的还可以使用如@name, @value, @href, @src, @class….
而 函数text()的意思则是取得节点包含的文本。比如:<div>hello<p>world</p>< /div>中,用”div[text()='hello']“即可取得这个div,而world则是p的text()。
函数position()的意思是取得节点的位置。比如“li[position()=2]”表示取得第二个li节点,它也可以被省略为“li[2]”。
不过要注意的是数字定位和过滤 条件的顺序。比如“ul/li[5][@name='hello']”表示取ul下第五项li,并且其name必须是hello,否则返回空。而如果用 “ul/li[@name='hello'][5]”的意思就不同,它表示寻找ul下第五个name为”hello“的li节点。
此外,“*”可以代替所有的节点名,比如用”/html/body/*/span”可以取出body下第二级的所有span,而不管它上一级是div还是p或是其它什么东东。
而 “descendant::”前缀可以指代任意多层的中间节点,它也可以被省略成一个“/”。比如在整个HTML文档中查找id为“leftmenu”的 div,可以用“/descendant::div[@id='leftmenu']”,也可以简单地使用“ //div[@id='leftmenu']”。
至于“following-sibling::”前缀就如其名所说,表示同一层的下一个节点。”following-sibling::*”就是任意下一个节点,而“following-sibling::ul”就是下一个ul节点。
2. Python3.10版 Win1064位无法安装lxml库
在练习xpath时,需要安装lxml模块,报错需要 Microsoft Visual C++ 14.0 吐槽一些教程:pip install wheel,安装无效果的 环境 window 10 python3 重装系统后,安装了最新的Python3.8.1,当使用pip安装lxml库的时候报错Microsoft Visual C++ 14.0 is required,
前提是:1.已安装python,2. 已安装好pip,3.已将python安装目录下的scripts目录(如D:\Python35\Scripts)添加到系统环境变量path里。
方法一:打开cmd,输入pip install lxml。如果安装成功的话,可以不用往下看了,人品太好了。通常呢,都会遇到各种错误,不是这不对,就是那里错,反正就是各种安装不了。
方绝蚂含法二:基本上,进到这里来看的人,应该都是已经掉到坑里的,所以直接从这里看起就可以。推并笑荐通过物宴lxml的.whl文件来进行安装。
3. python爬虫需要安装哪些库
一、 请求库
1. requests
requests 类库是第三方库,比 Python 自带的 urllib 类库使用方便和
2. selenium
利用它执行浏览器动作,模拟操作。
3. chromedriver
安装chromedriver来驱动chrome。
4. aiohttp
aiohttp是异步请求库,抓取数据时可以提升效率。
二、 解析库
1. lxml
lxml是Python的一个解析库,支持解析HTML和XML,支持XPath的解析方式,而且解析效率非常高。
2. beautifulsoup4
Beautiful Soup可以使用它更方便的从 HTML 文档中提取数据。
3. pyquery
pyquery是一个网页解析库,采用类似jquery的语法来解析HTML文档。
三、 存储库
1. mysql
2. mongodb
3. redis
四、 爬虫框架scrapy
Scrapy 是一套异步处理框架,纯python实现的爬虫框架,用来抓取网页内容以及各种图片
需要先安装scrapy基本依赖库,比如lxml、pyOpenSSL、Twisted
4. 如何搭建python+selenium自动化测试框架
selenium是一个web的自动化测试工具,不少学习功能自动化的同学开始首选selenium,相因为它相比QTP有诸多有点:
*免费,也不用再为破解QTP而大伤脑筋
*小巧,对于不同的语言它只是一个包而已,而QTP需要下载安装1个多G的程序。
*这也是最重要的一点,不管你以前更熟悉C、java、ruby、python、或都是C#,你都可以通过selenium完成自动化测试,而QTP只支持VBS
*支持多平台:windows、linux、MAC,支持多浏览器:ie、ff、safari、opera、chrome
*支持分布式测试用例的执行,可以把测试用例分布到不同的测试机器的执行,相当于分发机的功能。
关于selenium的基础知识与java平台的结合,我之前写过一个《菜鸟学习自动化测试》系列,最近学python,所以想尝试一下selenium的在python平台如何搭建;还好这方法的文章很容易,在此将搭建步骤整理分享。
搭建平台windows
准备工具如下:
-------------------------------------------------------------
下载python
-------------------------------------------------------------
如果你是新学python,哪果你没有要用的包是必须依赖于pyhton2.x 的,那么请毫不犹豫的选择python3.5吧!
window安装步骤:
1、下载python安装。
又报了个错:
Chromeversionmustbe>=27.0.1453.0 (Driverinfo:chromedriver=2.0,platform=WindowsNT5.1SP3x86)
说我chrome的版本没有大于27.0.1453.0,这个好办,更新到最新版本即可。
安装IEdriver
在新版本的webdriver中,只有安装了iedriver使用ie进行测试工作。
iedriver的下载地址在这里,记得根据自己机器的操作系统版本来下载相应的driver。
暂时还没尝试,应该和chrome的安装方式类似。
记得配置IE的保护模式
如果要使用webdriver启动IE的话,那么就需要配置IE的保护模式了。
把IE里的保护模式都选上或都勾掉就可以了。
5. python使用xpath(超详细)
使用时先安装 lxml 包
开始使用 #
和beautifulsoup类似,首先我们需要得到一个文档树
把文本转换成一个文档树对象
from lxml import etreeif __name__ == '__main__':doc='''
把文件转换成一个文档树对象
fromlxmlimportetree# 读取外部文件 index.htmlhtml = etree.parse('./index.html')result = etree.tostring(html, pretty_print=True)#pretty_print=True 会格式化输出print(result)
均会打印出文碧态档内容
节点、元素、属性、内容 #
xpath 的思想是通过 路径表达 去寻找节点。节点包括元素,属性,和内容
元素举例
html --->...div --->
这里我们可以看到,这里的元素和html中的标签一个意思。单独的元素是无法表达一个路径的,所以单独的元素不能独立使用
路径表达式 #
/ 根节点,节点分隔符,// 任意位置. 当前节点.. 父级节点@ 属性
通配符 #
* 任意元素@* 任意属性node() 任意子节点(元素,属性,内悔老源容)
谓语 #
使用中括号来限定元素,称为谓语
//a[n] n为大于零的整数,代表子元素排在第n个位置的 元素//a[last()] last() 代表子元素排在最后个位置的 元素//a[last()-] 和上面同理,代表倒数第二个//a[position()<3] 位置序号小于3,也就是前两个,这里我们可以看出xpath中的序列是从1开始//a[@href] 拥有href的 元素//a[@href='www..com'] href属性值为'www..com'的 元素//book[@price>2] price值大于2的元素
多个路含蠢径 #
用| 连接两个表达式,可以进行 或匹配
//book/title | //book/price
函数 #
xpath内置很多函数。更多函数查看 https://www.w3school.com.cn/xpath/xpath_functions.asp
contains(string1,string2)
starts-with(string1,string2)
ends-with(string1,string2) #不支持
upper-case(string) #不支持
text()
last()
position()
node()
可以看到last()也是个函数,在前面我们在谓语中已经提到过了
案例 #
定位元素 #
匹配多个元素,返回列表
fromlxmlimportetreeif__name__ =='__main__':doc='''
【结果为】
[<Element li at 0x2b41b749848>, <Element li at 0x2b41b749808>, <Element li at 0x2b41b749908>, <Element li at 0x2b41b749948>, <Element li at 0x2b41b749988>][] #没找到p元素
html = etree.HTML(doc)print(etree.tostring(html.xpath("//li[@class='item-inactive']")[0]))print(html.xpath("//li[@class='item-inactive']")[0].text)print(html.xpath("//li[@class='item-inactive']/a")[0].text)print(html.xpath("//li[@class='item-inactive']/a/text()"))print(html.xpath("//li[@class='item-inactive']/.."))print(html.xpath("//li[@class='item-inactive']/../li[@class='item-0']"))
【结果为】
b' third item \n 'None #因为第三个li下面没有直接text,Nonethird item #['third item'][<Element ul at 0x19cd8c4c848>][<Element li at 0x15ea3c5b848>, <Element li at 0x15ea3c5b6c8>]
使用函数 #
contains #
有的时候,class作为选择条件的时候不合适@class='....' 这个是完全匹配,当王爷样式发生变化时,class或许会增加或减少像active的class。用contains就能很方便
from lxml import etreeif __name__ == '__main__':doc='''
【结果为】
[<Element p at 0x23f4a9d12c8>, <Element li at 0x23f4a9d13c8>, <Element li at 0x23f4a9d1408>, <Element li at 0x23f4a9d1448>, <Element li at 0x23f4a9d1488>]
starts-with #
from lxml import etreeif __name__ == '__main__':doc='''
【结果为】
[<Element ul at 0x23384e51148>, <Element p at 0x23384e51248>, <Element li at 0x23384e51288>, <Element li at 0x23384e512c8>, <Element li at 0x23384e51308>, <Element li at 0x23384e51388>][<Element ul at 0x23384e51148>]
ends-with #
print(html.xpath("//*[ends-with(@class,'ul')]"))
【结果为】
Traceback (most recent call last):File"F:/OneDrive/pprojects/shoes-show-spider/test/xp5_test.py",line18,inprint(html.xpath("//*[ends-with(@class,'ul')]"))File"src\lxml\etree.pyx",line1582,inlxml.etree._Element.xpathFile"src\lxml\xpath.pxi",line305,inlxml.etree.XPathElementEvaluator.__call__File"src\lxml\xpath.pxi",line225,inlxml.etree._XPathEvaluatorBase._handle_resultlxml.etree.XPathEvalError: Unregisteredfunction
看来python的lxml并不支持有的xpath函数列表
upper-case #
和ends-with函数一样,也不支持。同样报错lxml.etree.XPathEvalError: Unregistered function
print(html.xpath("//a[contains(upper-case(@class),'ITEM-INACTIVE')]"))
text、last #
#最后一个li被限定了print(html.xpath("//li[last()]/a/text()"))#会得到所有的`<a>`元素的内容,因为每个<a>标签都是各自父元素的最后一个元素。#本来每个li就只有一个<a>子元素,所以都是最后一个print(html.xpath("//li/a[last()]/text()"))print(html.xpath("//li/a[contains(text(),'third')]"))
【结果为】
['fifth item']['second item', 'third item', 'fourth item', 'fifth item'][<Element a at 0x26ab7bd1308>]
position #
print(html.xpath("//li[position()=2]/a/text()"))#结果为['third item']
上面这个例子我们之前以及讲解过了
* 这里有个疑问,就是position()函数能不能像text()那样用呢
print(html.xpath("//li[last()]/a/position()"))#结果 lxml.etree.XPathEvalError: Unregisteredfunction
这里我们得到一个结论,函数不是随意放在哪里都能得到自己想要的结果
node #
返回所有子节点,不管这个子节点是什么类型(熟悉,元素,内容)
print(html.xpath("//ul/li[@class='item-inactive']/node()"))print(html.xpath("//ul/node()"))
【结果为】
[]['\n ', , '\n ', , '\n ', , '\n ', , '\n ', , ' 闭合标签\n ']
获取内容 #
**刚刚已经提到过,可以使用.text和text()的方式来获取元素的内容
from lxml import etreeif __name__ == '__main__':doc='''
【结果为】
['first item','second item','third item','fourth item','fifth item']first item18['\n ','\n ','\n ','\n ','\n ',' 闭合标签\n ']
看到这里,我们观察到text()和.text的区别。自己总结吧。不太好表达,就不表达了
获取属性 #
print(html.xpath("//a/@href"))print(html.xpath("//li/@class"))
【结果为】
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']['item-0active', 'item-1', 'item-inactive', 'item-1', 'item-0']
自定义函数 #
我们从使用函数的过程中得到结论,就是有的函数不支持,有的支持,那问题来了,到底那些方法支持呢。我们在lxml官网找到了答案。 https://lxml.de/xpathxslt.html 。lxml 支持XPath 1.0 ,想使用其他扩展,使用libxml2,和libxslt的标准兼容的方式。 XPath 1.0官方文档 以及其他版本的XPath文档 https://www.w3.org/TR/xpath/
lxml supports XPath1.0, XSLT1.0andthe EXSLT extensions through libxml2andlibxsltina standards compliant way.
除此之外,lxml还提供了自定义函数的方式来扩展xpath的支持度 https://lxml.de/extensions.html
from lxml import etree#定义函数def ends_with(context,s1,s2):return s1[0].endswith(s2)if __name__ == '__main__':doc='''
【结果为】
[<Element li at 0x2816ed30548>, <Element li at 0x2816ed30508>]['first item', 'third item']
形参s1会传入xpath中的第一个参数@class,但这里注意@class是个列表
形参s2会传入xpath中的第二个参数'active','active'是个字符串
官网例子 https://lxml.de/extensions.html
defhello(context, a):return"Hello %s"% afromlxmlimportetreens = etree.FunctionNamespace(None)ns['hello'] = helloroot = etree.XML('<a><b>Haegar</b></a>')print(root.xpath("hello('Dr. Falken')"))# 结果为 Hello Dr. Falken
6. Python3.70中etree.xpath使用问题
把from lxml import etree改成from lxml.etree import HTML,下面的etree.HTML改成HTML就行了。或者直接下物隐载一个正雹有etree模举蚂帆块的lxml版本
7. 如何安装python中的parsel
python-parsel
Parsel是一个使用XPath和CSS选择器(可选地与正则表达式结合)从HTML和XML提取数据的库
一、安装
官网:https://pypi.org/project/parsel/
pip安装:pip install parsel 默认安装的是最新版
pip install parsel=1.6.0 目前官方最新版本
PyCharm:File =》Setting =》Project:sintemple =》 Project:Interpreter =》点击右上角的加号(或者按快捷键Alt+Insert)=》在输入框中输入parsel,会出现一个只有parsel的一列,点击选择它 =》Install Package 等待安装完成就可以了(注:其中Specify version选中可以在下拉框中选择版本)
————————————————
三、csstranslator
TranslatorMixin
This mixin adds support to CSS pseudo elements via dynamic dispatch.Currently supported pseudo-elements are ::text and ::attr(ATTR_NAME).
①. xpath_attr_functional_pseudo_element(xpath, function)
Support selecting attribute values using ::attr() pseudo-element
②. xpath_element(selector)
③. xpath_pseudo_element(xpath, pseudo_element)
Dispatch method that transforms XPath to support pseudo-element
④. xpath_text_simple_pseudo_element(xpath)
Support selecting text nodes using ::text pseudo-element
XPathExpr(path=’’, element=’*’, condition=’’, star_prefix=False)
GenericTranslator
HTMLTranslator(xhtml=False)
四、utils
extract_regex(regex, text, replace_entities=True)
Extract a list of unicode strings from the given text/encoding using the following policies: * if the regex contains a named group called “extract” that will be returned * if the regex contains multiple numbered groups, all those will be returned (flattened) * if the regex doesn’t contain any group the entire regex matching is returned
flatten(sequence) → list
Returns a single, flat list which contains all elements retrieved from the sequence and all recursively contained sub-sequences (iterables). Examples: >>> [1, 2, [3,4], (5,6)] [1, 2, [3, 4], (5, 6)] >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, (8,9,10)]) [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10] >>> flatten([“foo”, “bar”]) [‘foo’, ‘bar’] >>> flatten([“foo”, [“baz”, 42], “bar”]) [‘foo’, ‘baz’, 42, ‘bar’]
iflatten(sequence) → Iterator
Similar to .flatten(), but returns iterator instead
shorten(text, width, suffix=’…’)
Truncate the given text to fit in the given width.
————————————————
原文链接:网页链接
8. python之jsonpath模块
安装方法:pip install jsonpath
官方文档: http://goessner.net/articles/JsonPath
jsonpath表达式与xPath表达式类似,用来解析多层嵌套的json数据。
JsonPath是Json版的XPath。
JsonPath 对码喊返于 JSON 来说,相当于 XPath 对于 XML
JsonPath用符号 $ 表示最外层对象,类似于Xpath中的 根元素
jsonPath可以用:
例如, address.* 表示address对象的所有属性,
book[*] 表示book数组的所有项目。 |
| [n] | 从数组中选择第n那一个元素。索引从0开始。 |
| [n1,n2,…] | 选择具有指定索引的数组元素,相当于上一个的多选。返回一个列表 |
| [start:end]
[start:] | 数组切片操作:
从start索引直到(但不包括)end索引选择数组元素。
省略 end ,则选择渗纤从start到数组结尾的所有元素。返回一个列表 |
| [:n] | 选择数组的前n个元素 |
| [-n:] | 选择数组的迟饥最后n个元素 |
| [?(expression)] | 过滤表达式,进行数据筛选 |
| [(expression)] | 使用表达式。
[(@.length-1)] 选择数组中的最后一项。
在这里 length 是指当前数组的长度,而不是名为的JSON字段 length 。 |
9. 怎么用python做自动化测试
这里以web自动化测试为例,简单介绍一下如何使用python进行web自动化测试,主要用到selenium这个框架,实验环境win10+python3.6,主要内容如下:
1.首先,安装selenium框架,这个直接在cmd窗口输入命令“pipinstallselenium”就行,如下,安装非常快:
2.安装完成后,还需要安装浏览器驱动程序,不然直接运行程序会报错,以谷歌浏览器chrome为例,需要下载chromedriver驱动程序,如下,这里chromedriver的版本必须要与自己平台浏览器的版本匹配:
下载完成后,是一个zip压缩包,里面就一个chromedriver.exe文件,这里需要将这个文件复制到python安装目录下,如下:
3.最后,我们就可以进行selenium框架测试了,测试代码如下,非常简单,创建一个webdriver,如果能正常打开对应网页,则说明selenium安装成功:
之后就可以直接定位相关元携余素,进行web自动化测试了,主要方法如下(共有8种),辩洞滚分别是id、name、classname、tagname、linktext、partiallinktext、xpath和cssselector,这里可以自行测试,相关资料非常丰富:
至此,我们就完成了pythonweb自动化测试框架selenium的安装和简单使用。总的来说,整个过程非常简单,只要你有一定的python基础,熟悉一下上面的安装过程,很快就能搭建好本地selenium自动化测试框架,网上也颤芹有相关教程和资料,介绍的非常详细,感兴趣的话,可以搜一下,希望以上分享的内容能对你有所帮助吧,也欢迎大家评论、留言进行补充。
10. Python lxml包下面的xpath基本用法
对于网页数据抓取,有BeautifulSoup、lxml以及正则表达式三种方法,其中正则表达式过于复杂,而beautifulsoup和lxml使用起来较为方便。以前简单使用过beautifulsoup(美味汤),后面为了扩展一下,熟悉一下lxml进行数据抓取。
先贴一个lxml的简仿早单框架:
其中,最主要的在于xpath路径的获取和解析,而XPath就是地址,具体地,就是需要知道所要寻找的内容处在哪个地址下。一般而言,我们可以根据开发者工具来定位我们需要的元素,然后右击选择其所在xpath,选择初步的路径,如下图所示,
这只是一种简单的方法,更重要的,需要掌握xpath的语法规则,下面分别论述。
使用xpath获取信息,主要包括获取本文和获取属性,基本用法为
对比可以看出,一个是采用text()获取文本,一个是采用@属性获取属性值。而前面标签后面方括号就是来对标签进行筛选的。一般而言,通过选择器可以获取诸如/html/body/div[@class="useful"]/ul/li/text()的信息,但是开头的信息没有标志性,采用//div[@class="useful"]/ul/li/text()即可。
这个地方即涉及到了xpath的语法选择,主要包括以下几点:
而在选择器方备缓雀面,包括以下几个
除此之外,在获取了一个元素之后,我们需要获取其下面元素的属性,即要对基于xpath获取的元素再次采用xpath,此时的获取方式为:
另外,我们也可以获取节点下面所有的字符串,方法为string(.),示例为:
懒得打字了,下面的截图来自W3Cschool, https://www.w3cschool.cn/lxml/_lxml-98h23fk0.html
主要的Xpath运算符包括以下:
按顺序选择等进一步的内容可以移步 https://www.w3cschool.cn/lxml/_lxml-eh1k3fk6.html
具体到不同的网页上,需要的其他哪陆知识就更多了,慢慢补充吧。不过似乎还是beautifulsoup好用一些,哈哈。
参考资料:
https://blog.csdn.net/weixin_39851008/article/details/109960957
https://www.w3cschool.cn/lxml/_lxml-98h23fk0.html