zoukankan      html  css  js  c++  java
  • Python爬虫之BeautifulSoup和requests

     用Python实现爬虫的包有很多,可以结合使用,但是目前个人觉得BeautifulSoup至少在看上去会更方便和美观一些。

    这里只涉及静态网页的爬取,暂不支持cookie、session等。

    • Python实现微博热搜榜的爬取

    微博热搜地址:https://s.weibo.com/top/summary

    微博热搜榜:https://s.weibo.com/top/summary?cate=realtimehot

    1. requests库:比urllib2模块更简洁,request支持http连接保持和连接池,支持使用cookie保持会话,支持文件上传,支持自动响应内容的编码,支持国际化的URL和POST数据自动编码。

    requests.get():用于请求目标网站,类型是一个HTTPresponse类型。

    还有:requests.post()、requests.put()、requests.delete()、requests.head()、requests.options()等方法。

    2. BeautifulSoup解析库:用于解析requests得到的网页,主要包括三种选择器:方法选择器(例如find、find_all等方法) 、 CSS选择器 、 节点选择器。

    3. 正式进入示例:

    (1). 首先导入需要的库:

    import requests
    from bs4 import BeautifulSoup

    (2). 通过url地址,使用requests包获取网页:

    url = 'https://s.weibo.com/top/summary'   #微博热搜
    web_html = requests.get(url)  #可以带更多dict格式的参数
    
    #可以带更多dict格式的参数,形成:https://s.weibo.com/top/summary?cate=realtimehot,多个dict参数会形成:...cate=realtimehot&param_2=value_2 格式
    web_html_2 = requests.get(url,params={'cate':'realtimehot'})  
    bs4_2 = BeautifulSoup(web_html_2.content, 'lxml')
    print(bs4_2.prettify())

    通过得到的web_html可以获得状态码、头信息、编码格式、url地址等:

    #print(web_html.status_code)  # 打印状态码 --- 200
    #print(web_html.headers)      # 打印头信息
    #print(web_html.content)   #以字节的方式显示,中文显示为字符形式
    #print(web_html.text)      #以text的方式显示
    #print(web_html.url)       #url地址
    #print(web_html.encoding)  #编码格式

    (3). 通过BeautifulSoup包进行解析(bs4形式的数据都可以进行的操作):

    • get_text()直接获取文本形式的信息:
    bs4 = BeautifulSoup(web_html.content, 'lxml')   #声明bs对象和解析器,返回解析后的网页信息
    # print(bs4)                              #看起来会比较混乱一点
    
    print(bs4.get_text()) #直接取文本,更简洁,属于字符串形式,看上去比较零散,因为有很多'
    '

    • prettify()粉墨登场,就变成有条理和顺序的网页文本了:
    print(bs4.prettify())                     #格式化代码,对齐、缩进、换行等
    out_str = bs4.prettify()
    with open('test_html.html','w',encoding='utf-8') as f:
        f.write(out_str)

    (4). 之后的操作均是在这个html文本上进行,也就是网页内容的获取 —— 涉及三种选择器:方法选择器(例如find、find_all等方法) 、 CSS选择器 、 节点选择器

    节点选择器

    # 直接标签内容
    print('title内容:
    ', bs4.title.string)   #以string格式打印出title标签中的内容
    
    print('title标签:
    ', bs4.title)   #返回标题值 <title></title>之间的值,因为一般只会有一个title
    print(type(bs4.title))    #注意:为bs4.element.Tag类型,同样属于bs4 ———— 也就是说你可以对这个类型的结果继续进行类似的选择操作

    # 也可以通过父子节点顺序读取,更为精准
    print('title标签:
    ', bs4.head.title)   #返回标题值 <title></title>之间的值,也可以嵌套获取
    print('title内容:
    ', bs4.head.title.string)
    
    print('head标签:
    ', bs4.head)    # <head></head>之间的值

    # -----------------------------------------------------------------------------

    这是原本的html内容:

    # p标签的属性,均属于字典格式
    print('第一个p标签:
    ', bs4.p)  # 结果发现只输出了一个p标签,但是HTML中有3个p标签,所以该选择器的特性:当有多个标签的时候,若不特别指定,它只返回第一个标签的内容,内容是bs4.element.Tag类型的,也就是可以继续操作的格式
    print('p标签的属性1:
    ', bs4.p.attrs['class']) #方式一:列表,['class']是因为该p标签有这个属性,如果没有,会报错
    print('p标签的属性2:
    ', bs4.p['class'])       #方式二:列表
    print('p标签的属性3:
    ', bs4.p.get('class'))   #方式三:列表,不是bs4类型哦get是字典值获取方法
    print('p标签的属性4:
    ', bs4.p.string)  #获取<p></p>中的网页文本
    # 嵌套使用
    print('嵌套使用:
    ', bs4.p.a)   #同样未指定,且存在多个a标签(相同子标签时),取第一个a标签;结果是bs4.element.Tag类型
    print('嵌套使用:
    ', bs4.p.option)
    # <!-- ... -->    #属于注释
    print('嵌套使用:
    ', bs4.p.contents)   #获取该p标签中的所有内容,包括注释,属于列表类型
    # 获取子节点,每个child是bs4.element.NavigableString类型,同样属于bs4类型中的
    print(bs4.p.children)   #迭代器
    for num, child in enumerate(bs4.p.children):
        print(num, child)
    
    # 获取父节点
    print(bs4.a.parent)   #默认第一个a标签的父节点
    # 还有类似的:
    # parents 属性:输出该标签的父节点、父节点的父节点、父节点的父节点的父节点......
    # next_sibings 属性:输出该标签后面的兄弟标签,注意兄弟标签指的是在同一父标签下的标签
    # previous_sibling属性:输出该标签前面的兄弟标签,注意兄弟标签指的是在同一父标签下的标签
    
    list_ = []
    for num, parent in enumerate(bs4.a.parents):
        print(num, parent)
        list_.append(parent)
    
    print(bs4.a.next_sibings)

    方法选择器

    find_all查询器:可根据标签名、属性、内容查找
    find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

    参数:

    所有attrs在使用的时候可以类似以下方式:

    • {'class': vaule1, 'id': value2, ...} 或者 attrs = {'class': vaule1, 'id': value2, ...} ,如果只有一个参数,也可以直接: class_ ='value1',(class_这是因为不能直接用class,会与python关键字冲突);
    • 如果只知道名称或者值value会变化,那么也可以只通过名称,但是要将value设置为True,例如:attrs = {'class': True};
    • 也可以将value设置为正则表达式:例如 attrs = {'class': re.compile(r'd+')}
    # 获取menu
    menu_div = bs4.find("div", class_='menu') # class_名称不要与class重复
    menu_href_list = []
    menu_title_list = []
    menu_list = []
    for kk in menu_div.find_all("a"):  #取值方式:menu_div.find_all("a")[i],每一个i对应得到的都是bs4.element.Tag类型
        href = kk['href']
        menu_title = kk['title']
        menu = kk.string               #当下<a></a>中的文本
        
        menu_href_list.append(href)
        menu_title_list.append(menu_title)
        menu_list.append(menu)
    print('menu的链接是:
    ',menu_href_list)
    print('menu的标题是:
    ',menu_title_list)
    print('menu的内容是:
    ',menu_list)

    类似的方法还有:

    # =============================================================================
    # find(name=None, attrs={}, recursive=True, text=None, **kwargs)
    # 和find_all类似,只不过find方法是返回单个元素,如果有多个相同的结果,则返回第一个元素
    # 
    # find_parents() find_parent()
    # find_parents()返回所有祖先节点,find_parent()返回直接父节点。
    # 
    # find_next_siblings() find_next_sibling()
    # find_next_siblings()返回后面所有兄弟节点,find_next_sibling()返回后面第一个兄弟节点。
    # 
    # find_previous_siblings() find_previous_sibling()
    # find_previous_siblings()返回前面所有兄弟节点,find_previous_sibling()返回前面第一个兄弟节点。
    # 
    # find_all_next() find_next()
    # find_all_next()返回节点后所有符合条件的节点, find_next()返回第一个符合条件的节点
    # 
    # find_all_previous() 和 find_previous()
    # find_all_previous()返回节点后所有符合条件的节点, find_previous()返回第一个符合条件的节点
    # =============================================================================
    def find_next(self, name=None, attrs={}, text=None, **kwargs)
    def find_all_next(self, name=None, attrs={}, text=None, limit=None, **kwargs)
    def find_next_sibling(self, name=None, attrs={}, text=None, **kwargs)
    def find_next_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)
    def find_previous(self, name=None, attrs={}, text=None, **kwargs)
    def find_all_previous(self, name=None, attrs={}, text=None, limit=None, **kwargs)
    def find_previous_sibling(self, name=None, attrs={}, text=None, **kwargs)
    def find_previous_siblings(self, name=None, attrs={}, text=None, limit=None, **kwargs)
    def find_parent(self, name=None, attrs={}, **kwargs)
    def find_parents(self, name=None, attrs={}, limit=None, **kwargs)
    def previous(self)

    例如:

    print(kk.find_parent())  #因为此时kk是上面的结果数据,属于bs4.element.Tag类型

    # ---------------------------------------------------------------

    好了,真正获取热搜的部分来了

    # 获取热搜
    timehot_rank_list = []
    timehot_href_list = []
    timehot_content_list = []
    timehot_num_list = []
    timehot_div = bs4.find("div", {'class':"data",'id':"pl_top_realtimehot"})  #如果是字典形式传参,则key要与html文件中的一致
    # timehot_tbody = timehot_div.find("tbody").get_text()   #获取文本形式的数据
    
    #timehot_tbody = timehot_div.find("tbody")   #可用
    timehot_tbody = timehot_div.tbody       #返回第一个tbody,如果只有一个tbody,也可以直接用
    return_str = ''
    for ii, mm in enumerate(timehot_tbody.find_all("tr")):
        rank = mm.find("td", class_ = "td-01 ranktop")
        td = mm.find("td", class_="td-02")
        timehot_href_list.append(td.a['href'])
        timehot_content_list.append(td.a.string)
        
        if ii==0:
            timehot_rank_list.append('0')
            timehot_num_list.append('9999999999')
            return_str = return_str +'	'+ '0' +'	'+ td.a.string +'	'+ td.a['href'] +'	'+ '9999999999' + '
    '
        else:
            timehot_rank_list.append(rank.string)
            timehot_num_list.append(td.span.string)
            return_str = return_str +'	'+ rank.string +'	'+ td.a.string +'	'+ td.a['href'] +'	'+ td.span.string + '
    '
        
    with open('微博热搜榜.txt','w',encoding='utf-8') as f:
        f.write(return_str)

    CSS选择器:这个更强,不过要对前端编程熟悉一点。

    先总结:

    • class选择要加 '.'
    • id选择要加 '#'
    • tag选择不用加特殊标号
    • 但是多重时必须要用空格隔开

    css:1重选择

    # 1重选择
    print(bs4.select('.data'))   # class选择
    print(bs4.select('table'))   # tag选择
    print(bs4.select('#pl_top_realtimehot'))   # id选择

    css:2重选择

    # 2重选择
    #选择class为data中的class为td-02的内容(也就是热搜标题)
    aa = bs4.select('.data .td-02')    #元素组成的列表
    print(bs4.select('.data .td-02'))
    
    bb = bs4.select('tr td')              #微博热搜中,一个tr有3个td;所有tr的td依次排列
    print(bs4.select('tr td'))            #标签选择,选择所有tr标签中的td标签,实现嵌套

    css:3重选择

    # 3重选择
    print(bs4.select('tr td i'))          #3重选择

    css:交叉选择

    #交叉选择
    print(bs4.select('#pl_top_realtimehot .td-02')) #'#'表示id选择器:选择id为pl_top_realtimehot中,class为td-02的内容
    print(bs4.select('#pl_top_realtimehot .td-02 a'))  #3重交叉选择,选出所有热搜的地址和内容
                     
    print(bs4.select('#pl_top_realtimehot .td-02 a')[0])  #列表取值,但是每个值又是bs4类型的哦
                     
    print(type(bs4.select('#pl_top_realtimehot .td-02 a')[0]))  #每一个内容的类别是:bs4.element.Tag,也属于可以继续find等选择的格式

    css:另一种实现嵌套选择的方式

    # 另一种实现嵌套的方式
    for tr in bs4.select('tr'):   #对每一个查到的tr,再进行选择;因为每一个的内容格式是:bs4.element.Tag
        print(tr.select('td'))
        print(tr.get_text())   #直接获取内容

    综合以上:真正取热搜的代码如下 ~~~

    import requests
    from bs4 import BeautifulSoup
    
    url = 'https://s.weibo.com/top/summary'   #微博热搜
    web_html = requests.get(url)  #可以带更多dict格式的参数
    
    bs4 = BeautifulSoup(web_html.content, 'lxml')   #声明bs对象和解析器,返回解析后的网页信息
    
    timehot_rank_list = []
    timehot_href_list = []
    timehot_content_list = []
    timehot_num_list = []
    timehot_div = bs4.find("div", {'class':"data",'id':"pl_top_realtimehot"})  #如果是字典形式,则key要与html文件中的一致
    # timehot_tbody = timehot_div.find("tbody").get_text()   #获取文本形式的数据
    
    #timehot_tbody = timehot_div.find("tbody")   #可用
    timehot_tbody = timehot_div.tbody       #返回第一个tbody,如果只有一个tbody,也可以直接用
    return_str = ''
    for ii, mm in enumerate(timehot_tbody.find_all("tr")):
        rank = mm.find("td", class_ = "td-01 ranktop")
        td = mm.find("td", class_="td-02")
        timehot_href_list.append(td.a['href'])
        timehot_content_list.append(td.a.string)
        
        if ii==0:
            timehot_rank_list.append('0')
            timehot_num_list.append('9999999999')
            return_str = return_str +'	'+ '0' +'	'+ td.a.string +'	'+ td.a['href'] +'	'+ '9999999999' + '
    '
        else:
            timehot_rank_list.append(rank.string)
            timehot_num_list.append(td.span.string)
            return_str = return_str +'	'+ rank.string +'	'+ td.a.string +'	'+ td.a['href'] +'	'+ td.span.string + '
    '
        
    with open('微博热搜榜.txt','w',encoding='utf-8') as f:
        f.write(return_str)
    View Code

    参考:

    https://www.cnblogs.com/Caiyundo/p/12507111.html

    https://www.jianshu.com/p/9cd4a7160784

  • 相关阅读:
    超微主板不识别M2-解决方案
    Centos7安装zookpeer
    PowerBI主题制作
    [python错误]UnicodeDecodeError: 'gbk' codec can't decode byte...
    使用Python批量合并PDF文件(带书签功能)
    Oracle使用超大SQL脚本文件恢复数据问题记录
    Linux Mint 18.2安装后需要进行的设置
    Excel使用SUMIF函数注意事项
    CSV文件分割与列异常处理的python脚本
    小程序例子
  • 原文地址:https://www.cnblogs.com/qi-yuan-008/p/12775271.html
Copyright © 2011-2022 走看看