python3.6爬取鳳凰網新聞-爬蟲框架式思維
一、序言
先前幾篇爬蟲的程式碼,是簡單的指令碼程式碼。在爬取小網頁覺得挺簡單、高效,但涉及複雜網頁的時候,就要考慮成熟的爬蟲框架與分散式。本篇部落格作為無框架式爬蟲和有框架式爬蟲的一個過渡,介紹具有框架式思維的爬蟲^_^。
二、框架結構圖
通常爬蟲分為五個部分,分別為:爬蟲排程器、URL管理器、網頁下載器、網頁解析器與資料儲存器。各部分的作用如下:
模組名稱 | 作用 |
---|---|
爬蟲排程器 | 統籌排程其他四個模組之間的協調工作,可以理解為爬蟲框架的司令部。 |
URL管理器 | 管理URL連結,維護新URL集合(未爬取的連結)與舊URL集合(已爬取的連結); 同時提供獲取新URL連結的介面。 |
網頁下載器 | 從URL管理器中獲取未爬取的連結,並下載網頁。 |
網頁解析器 | 將網頁下載器下載的網頁進行解析,從中提取新的連結給URL管理器,將提取的有效資料返回給資料儲存器。 |
資料儲存器 | 將網頁解析出的有效資料進行儲存。 |
三、物種管理器介紹
通常寫爬蟲,我們先分析url,寫出url管理器模組;然後寫網頁下載器,這個比較簡單;根據url網頁內容寫出網頁解析器模組;根據解析器的有效資料型別,選擇合適的儲存方式檔案或者資料庫。
(一)URL管理器
該模組主要維護兩個變數:以爬取的URL集合和未爬取的URL集合。之所以選擇集合是因為集合中元素不能重複的特點,這就給url進行了一個去重。
該模組的主要介面有:
- 判斷是否有待取的URL,方法定義為has_new_url()。
- 新增新的URL到未爬取的集合中,方法定義為:add_new_url(url),add_new_urls(urls)。
- 獲取未爬取的URL,方法定義為get_new_url()。
- 獲取未爬取的URL集合大小,方法定義為new_url_size()。
- 獲取已爬取的URL集合大小,方法定義為old_url_size()。
(二)網頁下載器
該模組主要用到的庫為requests,當然大家也可以根據自己需要選擇urllib庫等。具有的介面為:download(url)。
(三)網頁解析器
用於解析的庫主要用到BeautifulSoup、lxml等。提供一個parser對外的介面。
(四)資料儲存器
資料儲存器主要包括兩個方法:store_data(data)用於將解析出來的有效資料儲存到記憶體;output_html()用於將儲存的資料輸出到指定的檔案或者資料庫。
(五)爬蟲排程器
該模組首先要初始化其他四個模組,通過crawl(root_url)方法將起始連結傳入URL管理器,然後按照排程器流程執行各個模組,協調工作。
四、實戰演示
介紹了爬蟲框架基本的結構以及每個模組的作用和基本方法,我們拿一個網站實戰演練下。選取的網站為鳳凰網站的任意一個新聞連結(連結為文字,不能為視訊和圖片),提取其新聞標題和內容(內容格式不講究)。我們將
http://news.ifeng.com/a/20180429/57980009_0.shtml作為root_url。
該網頁的尾部有其他新聞,我們在網頁解析器裡面將這些連結提取出來進行爬取。
1.首先,我們根據上述所述,編寫UrlManager.py(URL管理器)。
#coding = utf-8
class UrlManager(object):
def __init__(self):
self.new_urls = set() #未爬取URL集合
self.old_urls = set()#未爬取URL集合
def has_new_url(self):
'''判斷是否有未爬取的URL
:return:
'''
return self.new_url_size() != 0
def get_new_url(self):
'''獲取一個未爬取的URL
:return
'''
new_url = self.new_urls.pop()
self.old_urls.add(new_url)
return new_url
def add_new_url(self, url):
'''
將新的URL新增到未爬取的URL集合中
:param url:單個URL
:return:
'''
if url is None:
return
if url not in self.new_urls and url not in self.old_urls:
self.new_urls.add(url)
def add_new_urls(self, urls):
'''
將新的URL新增到未爬取的URL集合中
:param urls:url 集合
:return:
'''
if urls is None or len(urls) == 0:
return
for url in urls:
self.add_new_url(url)
def new_url_size(self):
'''
獲取未爬取URL集合的大小
:return:
'''
return len(self.new_urls)
def old_url_size(self):
'''
獲取已爬取URL集合的大小
:return:
'''
return len(self.old_urls)
2.編寫網頁下載器HtmlDownloader.py
# coding:utf-8
import requests
class HtmlDownloader(object):
def download(self, url):
if url is None:
return None
user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.3964.2 Safari/537.36'
headers = {'User_Agent':user_agent}
try:
r = requests.get(url, headers=headers)
except Exception as e:
print(e)
if r.status_code==200:
r.encoding = 'utf-8'
return r.text
return None
3.編寫網頁解析器
我們首先開啟上述root_url網頁,查詢新聞標題與內容所在標記位置:
新聞內容所在位置,看下圖。
所以,網頁解析器HtmlParser.py程式碼如下:
#coding:utf-8
import re
import urllib
from bs4 import BeautifulSoup
class HtmlParser(object):
def parser(self, page_url, html_cont):
'''
用於解析網頁內容,抽取URL和資料
:param page_url:下載頁面的URL
:param html_cont:下載的網頁內容
:return:返回URL和資料
'''
if page_url is None or html_cont is None:
return
soup = BeautifulSoup(html_cont, 'html.parser')
new_urls = self._get_new_urls(page_url, soup)
new_data = self._get_new_data(page_url, soup)
return new_urls, new_data
def _get_new_urls(self, page_url, soup):
'''
抽取新的URL集合
:param page_url:下載頁面的URL
:param soup:soup
:return:返回新的URL集合
'''
new_urls = set()
#抽取符合要求的a標記
links = soup.find_all('a',href=re.compile(r'http://news.+\.shtml'))
for link in links:
#提取href屬性
new_url = link['href']
#拼接成完整網址
new_full_url = urllib.parse.urljoin(page_url, new_url)
new_urls.add(new_full_url)
return new_urls
def _get_new_data(self, page_url, soup):
'''抽取有效資料
:param page_url:下載頁面的URL
:param soup:soup
:return:返回有效資料
'''
try:
data = {}
print('抓取'+str(page_url))
data['url'] = page_url
title = soup.find('h1',{'id':'artical_topic'}).text
data['title'] = title
summary = soup.find('div',{'id':'main_content'}).text
#獲取tag中包含的所有新聞文字內容,將結果作為Unicode字串返回
data['summary'] = summary
return data
except:
print("連結不符合")
return None
4.編寫資料儲存器DataOutput.py,咱們姑且用json檔案儲存爬取的資料吧。
程式碼如下:
# coding:utf-8
import codecs, json
class DataOutput(object):
def __init__(self):
self.datas = []
def store_data(self, data):
if data is None:
return
self.datas.append(data)
def output_html(self):
fout = codecs.open('news.json','w',encoding='utf-8')
json.dump(self.datas,fp=fout,indent=4,ensure_ascii=False)#將所有資料寫入檔案。
SpiderMan.py
5.爬蟲排程器SpiderMan.py
# coding:utf-8
from DataOutput import DataOutput
from HtmlDownloader import HtmlDownloader
from HtmlParser import HtmlParser
from UrlManager import UrlManager
class SpiderMan(object):
def __init__(self):
self.manager = UrlManager()
self.downloader = HtmlDownloader()
self.parser = HtmlParser()
self.output = DataOutput()
def crawl(self, root_url):
#新增入口URL
self.manager.add_new_url(root_url)
#判斷url管理器中是否有新的url,同時判斷抓取多少url
while(self.manager.has_new_url() and self.manager.old_url_size()<100):
# try:
#從URL管理器獲取新的url
new_url = self.manager.get_new_url()
#HTML下載器下載網頁
html = self.downloader.download(new_url)
#HEML解析器抽取網頁資料
new_urls, data = self.parser.parser(new_url, html)
#將抽取的url新增到URL管理器中
self.manager.add_new_urls(new_urls)
#資料儲存器儲存檔案
self.output.store_data(data)
print("已經抓取%s個連結"%self.manager.old_url_size())
# except Exception as e:
# print(e)
# print("Crawl failed")
#資料儲存器將檔案輸出成指定格式
self.output.output_html()
if __name__ == "__main__":
Spider_man = SpiderMan()
Spider_man.crawl('http://news.ifeng.com/a/20180429/57980009_0.shtml')
我們將上述檔案放到同一個資料夾:
然後就可以執行排程器py檔案,設定爬取連結為100個,
程式執行效果。
最後我們看下news.json檔案
五、結束語
本篇部落格的重點是理解爬蟲框架,大家可以根據自己的需要修改程式,使之符合自己的需求。
最後依然希望大家多多關注,後續更新更精彩。