1.前言——网页解析器的种类现在,我已经会使用 python 模拟浏览器进行一些 Https 的抓包,发请求了,那么根据我们第一篇所说的结构: 网页下载器() -> 网页解析器() 的流程,接下来该说网页解析器了。 在网页解析器中,我们一般有这四个:
我们知道,在发送请求完之后,服务器会返回给我们一堆源代码。 可以看到很多乱七八糟的东西,明显这可不是我们想要的,所以我们要提取出有价值的数据!!! 也就是在服务器返回给我们的源码之中我们要进行过滤,匹配或者说提取 只拿到我们想要的就好,其它就丢掉(卧槽,无情…)。 说到过滤,匹配,提取之类的操作,很多人就想到了鼎鼎大名的正则表达式了。 正则表达式就是定义一些特殊的符号来匹配不同的字符。 但是一个字!真特么难… 再见 2. 正则表达式介绍哈哈,开玩笑,虽然我不喜欢用正则,但是也还是把常用的列出来! 那么python是怎么使用正则表达式的呢?就要用python 的 re库了。 re模块re 模块用正则表达式最常用的函数。 是这个方法: re.match(x,y) 主要传入两个参数:
例如: 想要从content 中拿到一个数字 import re content = 'CSDNzoutao has 100 bananas' res = re.match('^CS.*(\d+)\s.*s$',content) print(res.group(1)) 可以运行看看。 其次,在python爬虫中比较常用到的组合是:
就是这三个字符,它表示的就是匹配任意字符。 但是 .*?的 . 代表所有的单个字符,除了 \n \r 如字符串有换行了怎么办呢? content = """CSDNzoutao has 100 bananas""" 就需要用到 re 的匹配模式了。 说来也简单,直接用 re.S: import re content = """CSDNzoutao has 100 bananas""" res = re.match('^CS .*?(\d+)\s.*s$',content,re.S) print(res.group(1)) 结果: 再来说说 re 的另一个常用到的方法吧:
这个主要就是把匹配符封装。 import re content = "CSDNzoutao has 100 bananas" pattern = re.compile('CS .*?(\d+)\s.*s',re.S) res = re.match(pattern,content) print(res.group(1)) compile 就是数据之类的封装一下,方便下面调用。 更多正则请参考正则表达式详解。 2.1 requests+re正则 爬虫实战:好吧,虽然我很不情愿用正则,但是多少也是要写一个实战例子的是吧。 就使用 requests 和 re正则解析器来写一个爬虫。 因为前一篇就是它,我就不写了。 好了 ,关于 re 模块和正则表达式就介绍完啦。 因为我们在现在的爬虫中,很多不用正则表达式了,毕竟我也看不明白。所以我们一般使用另外的网页解析器。 3.BeautifulSoup库的介绍最常用的,就是第三方插件——BeautifulSoup库 了。比正则好记啊! 既然是第三方库,那么要安装一下这个库:
值得一提的是啊,在 beautifulsoup 库中,还支持不同的解析器。(很多人被搞懵逼了,记住我说的bs是库!仓库里面放不同的解析器,很合理吧?) 比如: 1.解析库:BeautifulSoupBeautifulSoup号称Python中最受欢迎的HTML解析库之一。 lxml这个库可以用来解析HTML和XML文档,以非常底层的实现而闻名,大部分源码都是C语言写的,虽然学习这东西要花一定的时间,但是它的处理速度非常快。 HTML parser这是python自带的解析库,所以很方便。 2.解析器:其实上面提到的另外的这两个库,又可以作为BeautifulSoup库的解析器。 下面对BeautifulSoup中的 各种 html 解析器的优缺点对比: Python’s html.parser 默认用的是这个 使用语法: BeautifulSoup(markup,"html.parser") 优点
缺点
lxml’s HTML parser 最推荐 使用语法 BeautifulSoup(markup,"lxml") 优点
缺点
lxml’s XML parser 最推荐 使用语法 BeautifulSoup(markup, "lxml-xml") 或者 BeautifulSoup(markup,"xml") 优点
缺点
html5lib :最不常用 使用语法 BeautifulSoup(markup, "html5lib") 优点
缺点
如图: 所以,我们在使用beautifulsoup库的时候,必须要指定一个解析器。而lxml和html.parser都可以考虑拿来做解析器。 l其中 xml解析器通常更快,如果可以,我建议您安装并使用lxml以提高速度。 不同的解析器将为其生成不同的BeautifulSoup树,所以先来使用一个例子, 体验一下beautifulsoup 的一些常用的方法。 3.1 BeautifulSoup 常用方法口水话我也懒得说了,其他博文估计讲得很清楚,我就举个栗子,常用方法已经用*** 标出。 假设我们使用网页下载器弄回来的网页是这样的:
html_doc = """ <html> <head> <title>睡鼠的故事</title> </head> <body> <p class="title" id="No0.1"><b>第一章bs示例</b></p> <p class="story">从前有三个小妹妹,她们的名字是: <a href="http:///elsie" class="sister" id="link1">1张三</a>, <a href="http:///lacie" class="sister" id="link2">2李四</a> and <a href="http:///tillie" class="sister" id="link3">3Jack</a>; and他们住在井底. </p> <div class="story">省略一万字。。。 <ul id="producers"> <li class="producerlist"> <div class="name">香蕉</div> <div class="number">100斤</div> </li> <li class="producerlist"> <div class="name">苹果</div> <div class="number">200斤</div> </li> </ul> </div> </body> </html> """
# 2.引入库 from bs4 import BeautifulSoup # 3.解析该html页面 soup = BeautifulSoup(html_doc, 'html.parser') 然后我们要做的就是从这个对象直接获取我们要的内容了。
获取标题的内容: print(soup.title.string) # 睡鼠的故事 print(soup.head)#告诉它想获取的tag的name,是标签名,而不是标签的name属性 #<head> #<title>睡鼠的故事story</title> #</head> 获取超链接 a 标签或超链里面的文本内容: print(soup.a) # 通过点取属性的方式只能获得当前名字的【第一个a标签】 # <a class="sister" href="http:///elsie" id="link1">1张三</a> print(soup.a.string) # 通过.string获取a标签中的文本内容。 # 1张三 获取所有超链接: print(soup.find_all('a')) # 所有的<a>标签,或是通过名字得到更多内容 # [<a class="sister" href="http:///elsie" id="link1">1张三</a>, <a class="sister" href="http:///lacie" id="link2">2李四</a>, <a class="sister" href="http:///tillie" id="link3">3Jack</a>] 获取 id 为 link2 的超链接: print(soup.find(id="link2")) #<a class="sister" href="http:///lacie" id="link2">2李四</a> 获取网页中所有的内容: print(soup.get_text()) # 获取网页中所有的文本内容 (*)去除多余空白内容: stripped_strings方法: for string in soup.stripped_strings: print(repr(string)) (空格的行会被忽略掉,段首和段末的空白会被删除)
soup.find_all('b') # 查找文档中所有的<b>标签 import re for tag in soup.find_all(re.compile("^b")): # 还可以配合正则作参数,用 match()调用 print(tag.name) a2 = soup.find_all(id="link2") print("===========================",a2,type(a2)) def has_six_characters(css_class): return css_class is not None and len(css_class) == 6 print(soup.find_all(class_=has_six_characters)) print(soup.find_all("a", class_="sister")) # 搜索内容里面包含“Elsie”的<a>标签: print(soup.find_all("a", string="Elsie")) # 使用 limit 参数限制返回结果的数量 print(soup.find_all("a", limit=2)) # 只想搜索tag的直接子节点,可以使用参数 recursive=False print(soup.html.find_all("title", recursive=False))
唯一的区别:find_all()方法的返回的值是包含元素的列表,而find()方法直接返回的是一个结果。 举例子: print(soup.find_all('title', limit=1)) # 跟下面一句等效。 print(soup.find('title')) # ** 基于文本内容的查找必须用到参数text p = soup.find("class",class_="name") # None print("==============",p) # 虽说不报错,但返回的是空,匹配不到值。 p2 = soup.find("class",attrs={"class":"name"}) # None 匹配不到值。 print("++++++++++++++++++++++",p2) # 层次结构 p3 = soup.find('div').find('li').find("div").text # 这样写才是对的! print("-----------------",p3) # 香蕉 print(soup.get_text()) # 只要文本内容 print(soup.i.get_text()) # find_all_next() 方法返回所有符合条件的节点, # find_next() 方法返回第一个符合条件的节点: print(soup.a.find_next("p")) 是不是不太好理解? bs也这么想的,所以除了find方法、find_all()之外,如果你对css比较熟悉,就可以使用select ()方法
print(soup.select("title")) # 逐层查找 print(soup.select("html head title")) # ***找到某个tag标签下的直接子标签 print(soup.select("p > #link1")) # 通过CSS的类名查找: *** print(soup.select(".sister")) # 通过id查找: print(soup.select("#link1")) # 同时用多种CSS选择器查询元素: print(soup.select("#link1,#link2")) # 通过是否存在某个属性来查找: print(soup.select('a[href]')) # 通过属性的值来查找: print(soup.select('a[href$="tillie"]')) # *** 取查找元素的文本值 print(soup.select('a[class="sister"]')[0].text) # 返回查找到的元素的第一个 print(soup.select_one(".sister")) # 格式化输出: prettify()方法-对象或节点都可以调用。 print(soup.prettify()) print(soup.a.prettify()) 基本上以上就是python爬虫中 BeautifulSoup 常用的方法,有了它,再也不用担心不会正则表达式了。 整个例子源码: #!/usr/bin/python3 # 1.得到一个html页面 html_doc = """ <html> <head> <title>睡鼠的故情</title> </head> <body> <p class="title" id="No0.1"><b>第一章bs示例</b></p> <p class="story">从前有三个小妹妹,她们的名字是: <a href="http:///elsie" class="sister" id="link1">1张三</a>, <a href="http:///lacie" class="sister" id="link2">2李四</a> and <a href="http:///tillie" class="sister" id="link3">3Jack</a>; and他们住在井底. </p> <div class="story">省略一万字。。。 <ul id="producers"> <li class="producerlist"> <div class="name">香蕉</div> <div class="number">100斤</div> </li> <li class="producerlist"> <div class="name">苹果</div> <div class="number">200斤</div> </li> </ul> </div> </body> </html> """ # 2.引入库 from bs4 import BeautifulSoup # 3.解析该html页面 soup = BeautifulSoup(html_doc, 'html.parser') # 一、基本查找元素的方式-可以直接根据html的标签来取值。 print(soup.head) # 告诉它你想获取的tag的name,是标签名字,而不是标签的name属性!!! print(soup.title) print(soup.body.b) # 获取<body>标签中的第一个<b>标签 print(soup.a) # 通过点取属性的方式只能获得当前名字的【第一个a标签】: print(soup.find_all('a')) # 所有的<a>标签,或是通过名字得到更多内容 head_tag = soup.head print('===========',head_tag.contents) # 以列表的方式输出 #title_tag = head_tag.contents[0] # 字符串没有.contents 属性,因为字符串没有子节点: #print(title_tag.contents) # (*)去除多余空白内容: 空格的行会被忽略掉,段首和段末的空白会被删除 for string in soup.stripped_strings: print(repr(string)) # (*)二、[更强的查找元素-过滤器find_all()方法。是模糊匹配。返回一个列表list可遍历,没有找到目标是返回空列表], soup.find_all('b') # 查找文档中所有的<b>标签 import re for tag in soup.find_all(re.compile("^b")): # 还可以配合正则作参数,用 match()调用 print(tag.name) a2 = soup.find_all(id="link2") print("===========================",a2,type(a2)) def has_six_characters(css_class): return css_class is not None and len(css_class) == 6 print(soup.find_all(class_=has_six_characters)) print(soup.find_all("a", class_="sister")) # 搜索内容里面包含“Elsie”的<a>标签: print(soup.find_all("a", string="Elsie")) # 使用 limit 参数限制返回结果的数量 print(soup.find_all("a", limit=2)) # 只想搜索tag的直接子节点,可以使用参数 recursive=False print(soup.html.find_all("title", recursive=False)) # 三、find()方法—返回文档中符合条件的标签,是一个ResultSet结果集,找不到目标时,返回 None .所以soup.find()后面可以直接接.text或get_text()来获得标签中的文本内容。 print(soup.find_all('title', limit=1)) print(soup.find('title')) # *** [基于文本内容的查找必须用到参数text] p = soup.find("class",class_="name") # None print("==============",p) # 虽说不报错,但返回的是空,匹配不到值。 p2 = soup.find("class",attrs={"class":"name"}) # None 匹配不到值。 print("++++++++++++++++++++++",p2) # 层次 p3 = soup.find('div').find('li').find("div").text # 这样写才是对的! print("-----------------",p3) # 香蕉 # 唯一的区别:find_all()方法的返回结果是值包含元素的列表,而find()方法直接返回结果. # find_all_next() 方法返回所有符合条件的节点, # find_next() 方法返回第一个符合条件的节点: print(soup.a.find_next("p")) # 三、CSS选择器:.select()方法中传入字符串参数-精确定位,返回一个列表list print(soup.select("title")) # 逐层查找 print(soup.select("html head title")) # ***找到某个tag标签下的直接子标签 print(soup.select("p > #link1")) # *** 通过CSS的类名查找: print(soup.select(".sister")) # 通过id查找: print(soup.select("#link1")) # 同时用多种CSS选择器查询元素: print(soup.select("#link1,#link2")) # 通过是否存在某个属性来查找: print(soup.select('a[href]')) # 通过属性的值来查找: print(soup.select('a[href$="tillie"]')) # *** 取查找元素的文本值 print(soup.select('a[class="sister"]')[0].text) # 返回查找到的元素的第一个 print(soup.select_one(".sister")) # 格式化输出: prettify()方法-对象或节点都可以调用。 print(soup.prettify()) print(soup.a.prettify()) # ***只想得到tag中包含的文本内容,那么可以用get_text()方法,结果作为Unicode字符串返回 markup = '<a href="http:///">\nI linked to <i></i>\n</a>' soup = BeautifulSoup(markup,"html.parser") print(soup.get_text()) # 只要文本内容 print(soup.i.get_text()) 主要学会几个东西,取标签,取标签里面的文本内容,取多个标签,取单个标签,以及最常用的find_all() 、find()、 a.[0].text 和.get_text()几个方法的使用就可以了。 老规矩,也整个爬虫案例看看,对比一下我们的re模块和bs哪个好用些? 3.2 BeautifulSoup + reuqests实战最近电影慌,就拿豆瓣电影 Top 250的那个来进行爬取啦。 BeautifulSoup + reuqests豆瓣电影 Top 250实战爬虫 好了,以上就是网页解析器的各种解释和使用。下一篇总结写BeautifulSoup 爬虫的关键做法,怎么定位标签和提取想要的内容?参考地址: https://blog.csdn.net/ITBigGod/article/details/102859854 当然你也可以直接去看 BS4的官方文档。。 bs的英文文档: https://www./software/BeautifulSoup/bs4/doc/#installing-a-parser 解析器之间的区别: https://www./software/BeautifulSoup/bs4/doc/#differences-between-parsers |
|