1. 程式人生 > >python3.6爬取鳳凰網新聞-爬蟲框架式思維

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檔案


五、結束語

        本篇部落格的重點是理解爬蟲框架,大家可以根據自己的需要修改程式,使之符合自己的需求。

        最後依然希望大家多多關注,後續更新更精彩。