用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¶m_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()) #直接取文本,更简洁,属于字符串形式,看上去比较零散,因为有很多'\n'
- 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内容:\n', bs4.title.string) #以string格式打印出title标签中的内容 print('title标签:\n', bs4.title) #返回标题值 <title></title>之间的值,因为一般只会有一个title print(type(bs4.title)) #注意:为bs4.element.Tag类型,同样属于bs4 ———— 也就是说你可以对这个类型的结果继续进行类似的选择操作
# 也可以通过父子节点顺序读取,更为精准 print('title标签:\n', bs4.head.title) #返回标题值 <title></title>之间的值,也可以嵌套获取 print('title内容:\n', bs4.head.title.string) print('head标签:\n', bs4.head) # <head></head>之间的值
# -----------------------------------------------------------------------------
这是原本的html内容:
# p标签的属性,均属于字典格式 print('第一个p标签:\n', bs4.p) # 结果发现只输出了一个p标签,但是HTML中有3个p标签,所以该选择器的特性:当有多个标签的时候,若不特别指定,它只返回第一个标签的内容,内容是bs4.element.Tag类型的,也就是可以继续操作的格式 print('p标签的属性1:\n', bs4.p.attrs['class']) #方式一:列表,['class']是因为该p标签有这个属性,如果没有,会报错 print('p标签的属性2:\n', bs4.p['class']) #方式二:列表 print('p标签的属性3:\n', bs4.p.get('class')) #方式三:列表,不是bs4类型哦,get是字典值获取方法 print('p标签的属性4:\n', bs4.p.string) #获取<p></p>中的网页文本
# 嵌套使用 print('嵌套使用:\n', bs4.p.a) #同样未指定,且存在多个a标签(相同子标签时),取第一个a标签;结果是bs4.element.Tag类型 print('嵌套使用:\n', bs4.p.option) # <!-- ... --> #属于注释 print('嵌套使用:\n', 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的链接是:\n',menu_href_list) print('menu的标题是:\n',menu_title_list) print('menu的内容是:\n',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 +'\t'+ '0' +'\t'+ td.a.string +'\t'+ td.a['href'] +'\t'+ '9999999999' + '\n' else: timehot_rank_list.append(rank.string) timehot_num_list.append(td.span.string) return_str = return_str +'\t'+ rank.string +'\t'+ td.a.string +'\t'+ td.a['href'] +'\t'+ td.span.string + '\n' 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 +'\t'+ '0' +'\t'+ td.a.string +'\t'+ td.a['href'] +'\t'+ '9999999999' + '\n' else: timehot_rank_list.append(rank.string) timehot_num_list.append(td.span.string) return_str = return_str +'\t'+ rank.string +'\t'+ td.a.string +'\t'+ td.a['href'] +'\t'+ td.span.string + '\n' with open('微博热搜榜.txt','w',encoding='utf-8') as f: f.write(return_str)
参考: