beautifulSoup模块
在之前的爬虫程序中 我们编写了大量的正则表达式来获取数据,效率非常低
beautifulSoup是一个第三方模块 专门用于解析XML文档
目前最稳定的是bs4 版本
pip install bs4
from bs4 import BeautifulSoup # 第一个参数为要解析的文档数据 可以是字符串 也可以是一个文件对象 # 第二个参数表示要使用的解析器 每个解析器有不同的特点 推荐是用lxml 速度快 pip install lxml soup = BeautifulSoup(html_doc,"lxml") soup代表整个文档 #查找内容 tag = soup.body # 拿到html中body标签 # 一个标签分为三个部分 # 标签名字 标签属性 标签内容 print(type(tag)) #Tag print(tag.name) #标签名 print(tag.text) #标签内容 print(tag.attrs) #标签属性 返回是一个字典 # 使用点语法来查找标签 要注意 查找范围是全文 只能找到第一个名字匹配的标签 tag = soup.a #拿到第一个a标签 print(tag.attrs.get("href")) #拿到属性href的内容 # 嵌套选择 print(soup.p.b.text) #p标签中b标签的文本内容 # 获取子节点 #注意:a标签,还有img标签不能放到head标签中 for i in soup.p.children: # 返回一个迭代器 iterator print(i) for i in soup.p.contents: # 返回一个列表 print(i) # 获取父标签 print(soup.p.parent) # 获取所有的父辈标签 #soup.p.parents #返回一个生成器 generator for i in soup.p.parents: print(i.name) # 获取所有子孙标签 与soup.p.contents不同之处在于 会把所有子孙标签全部拆出来 包括文本内容 #soup.p.descendants #返回一个生成器 generator for i in soup.p.descendants: print(i.name) # 文本内容没有name属性,返回None # 获取兄弟标签 文本也被当做是一个节点 # 下一个兄弟 print(soup.a.next_sibling) # 之后的兄弟们 print(list(soup.a.next_siblings)) # 上一个兄弟 print(soup.a.previous_sibling) # 之前的兄弟们 print(list(soup.a.previous_siblings))
# 过滤器 # find_all查找所有匹配的标签 # 按照名字匹配 可以传一个名字 或 一个列表 print(soup.find_all("a")) #查找所有的a标签 print(soup.find_all(["a","p"])) #查找所有的a和p标签 # 找id 为link 的a标签 print(soup.find_all("a",attrs={"id":"link"})) print(soup.find_all(name="a",id="link")) # 找class为sister 的a标签 print(soup.find_all("a",attrs={"class":"sister"})) # 注意如果要按照条件为class来查找 需要使用class_ 因为class是关键字 print(soup.find_all(name="a",class_="sister brother")) # 只能找到类名完全匹配的如:<a class="sister brother"> print(soup.find_all(name="a",class_="sister")) #只要类名带有sister就能找到 # 如果属性带有特殊符号 可以把条件装在attrs中 print(soup.find_all(name="a",attrs={"data-a":"sister"})) # 指定文本 print(soup.find_all(name="a",text="Elsie")) # 过滤器 # 标签名称中带有b字母的标签 # 正则匹配 c = re.compile("b") print(soup.find_all(name=c)) # 列表 print(soup.find_all(name=["body","a"])) # True 表示所有标签 print(soup.find_all(True)) # 所有具备id属性的标签 print(soup.find_all(id=True)) # 方法匹配(写个函数来过滤) def myFilter(tag):# 必须只能有一个参数 参数表示要过滤的标签 #标签名是a 文本内容不是"Elsie" 标签含有id属性 return tag.name == "a" and tag.text != "Elsie" and tag.has_attr("id") print(soup.find_all(myFilter)) #返回满足条件的所有数据 print(soup.find_all(myFilter,limit=1)) #返回一条数据 #获取所有a标签 print(soup.find_all(name="a")) # 五种过滤器: 字符串、正则表达式、列表、True、方法
2.find()
#find( name , attrs , recursive , text , **kwargs ) find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all() 方法来查找<body>标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.下面两行代码是等价的: soup.find_all('title', limit=1) # [<title>The Dormouse's story</title>] soup.find('title') # <title>The Dormouse's story</title> 唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果. find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None . soup.head.title 是 tag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find() 方法: soup.head.title # <title>The Dormouse's story</title> soup.find("head").find("title") # <title>The Dormouse's story</title> # recursive 是否递归
3、CSS选择器
#该模块提供了select方法来支持css,详见官网:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html#id37 html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"> <b>The Dormouse's story</b> Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1"> <span>Elsie</span> </a> <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; <div class='panel-1'> <ul class='list' id='list-1'> <li class='element'>Foo</li> <li class='element'>Bar</li> <li class='element'>Jay</li> </ul> <ul class='list list-small' id='list-2'> <li class='element'><h1 class='yyyy'>Foo</h1></li> <li class='element xxx'>Bar</li> <li class='element'>Jay</li> </ul> </div> and they lived at the bottom of a well. </p> <p class="story">...</p> """ from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #1、CSS选择器 print(soup.p.select('.sister')) print(soup.select('.sister span')) print(soup.select('#link1')) print(soup.select('#link1 span')) print(soup.select('#list-2 .element.xxx')) print(soup.select('#list-2')[0].select('.element')) #可以一直select,但其实没必要,一条select就可以了 # 2、获取属性 print(soup.select('#list-2 h1')[0].attrs) # 3、获取内容 print(soup.select('#list-2 h1')[0].get_text())
案例:爬取汽车之家新闻
import requests from bs4 import BeautifulSoup url = "https://www.autohome.com.cn/news/{page}/" # 过滤标签 def filter(tag): return tag.name == "li" and tag.has_attr("data-artidanchor") # 获取新闻列表 def get_list_paget(url): print(url) resp = requests.get(url,headers={"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"}) soup = BeautifulSoup(resp.text,"lxml") lis = soup.find_all(filter) for t in lis: print("https:"+t.a.attrs.get("href")) print("https:"+t.img.attrs.get("src")) print(t.h3.text) print(t.span.text) print(t.em.text) print(t.p.text) get_list_paget(url.format(page = 1))
案例:爬取中关村测评数据
''' 分析可知 1.首页数据存放在html文档中 http://labs.zol.com.cn/ 2.后续加载的数据是通过ajax请求,刷新文档,返回数据类型为json http://labs.zol.com.cn/router.php?c=TestChannel_Default&a=GetChannelNew&module=new&page=2 ''' import requests from bs4 import BeautifulSoup #爬取第一页的数据 url = 'http://labs.zol.com.cn/' user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36' resp = requests.get(url,headers = {'user-agent':user_agent}) soup = BeautifulSoup(resp.text,'lxml') res = soup.find_all(name='li',class_='content-list-item') for i in res: #连接 print(i.a.get('href')) #标题 print(i.div.div.text) #简介 print(i.p.text) #评论数 n = i.find(name='div',class_='icon info-comment') print(n.text) #时间 print(i.span.text) #单张图片获取 print(i.a.img.attrs.get('src')) #多张图片获取 res = i.find(True,class_='pics-list clearfix') if res: imgs = i.find_all(name='img') for img in imgs: print(img.attrs.get('src')) import time #爬取加载更多的数据 base_url = 'http://labs.zol.com.cn/router.php?c=TestChannel_Default&a=GetChannelNew&module=new&page={page}' for i in range(2,4): #爬取第二页和第三页的数据 resp = requests.get(base_url.format(page=i)) print(resp.json()) time.sleep(1)