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

原文地址:https://www.cnblogs.com/qi-yuan-008/p/12775271.html