1. 程式人生 > >爬取多頁資訊——爬取自己CSDN部落格

爬取多頁資訊——爬取自己CSDN部落格

在學完莫煩B站的爬蟲視訊的3.3節之後,我完成了這個專案,感覺收穫還是不小的。

體會是自己練手感覺進步挺快,能做出一個小專案給了我一些自信心,中間幾個棘手的問題

1如何獲取下一頁:分析我的主頁的HTML,發現有一個總的文章數、有pagesize(每頁最多顯示幾篇文章),有此兩值就可以得到總的頁數。再發現每一頁的url是這樣的https://blog.csdn.net/liuchengzimozigreat/article/list/2,每頁連結基本一致,最後一個數字代表的是第幾頁,於是就可以用字串拼接來得到所有分頁的url;

2如何獲取各個文章的資訊:這主要去分析具體的HTML,看你需要的資訊具體在哪兒,然後用解析器幫助你得到想要的HTML模組,然後具體再去每個模組中提取資訊;

3我要存什麼資訊、怎麼存:好吧,這個問題其實是我最後碰到的,前面程式做的差不多了,最後才想著我要怎麼存、存什麼,當然之前你可能想過一些,但如果想的不是很清楚的話,這個時候你就要停下來好好想想了。這個問題關係有點大,如果一開始就瞭然於心,就可以讓我們思路更加順暢;

4正則表示式的問題:正則表示式還是推薦這篇文章,我看完莫煩的正則表示式之後,其實立馬就忘了好多,還是碰到具體問題之後,去看那篇文章中總結的表,於是從一開始啥也不會,到後面正則表示式越來越得心應手。

下面是我的程式碼,感覺寫得很亂,以後還是多寫一些函式才好,都寫在主函式裡,讓人不想看了簡直:

# Download amazing pictures from national geographic
from bs4 import BeautifulSoup
import requests
import re
import datetime
import pandas as pd
import os

# 提取第一頁至最後一頁的url
def get_allpages_url(page_info):
	'''<script>
    var currentPage = 1;
    var baseUrl = 'https://blog.csdn.net/liuchengzimozigreat/article/list' ;
    var pageSize = 20 ;
    var listTotal = 26 ;
    var pageQueryStr = '';
    function getAllUrl(page) {
        return baseUrl + "/" + page + pageQueryStr;
    }
	</script>'''
	# 以上是script_lst[-9]的資訊,有三個數:currentPage, pageSize, listTotal  '\d'只查詢數字一次,'\d+'則往後查詢數字無限次
	currentPage, pageSize, listTotal = [int(num) for num in re.findall(r'\d+', page_info)]
	pageNum = listTotal//pageSize + 1 # 總的網頁數
	print('總的網頁數:', pageNum, '總的文章數:', listTotal, 'pageSize:', pageSize)
	baseUrl = re.findall(r'\'(.*)\'', page_info)[0] # .*在正則表示式中表示匹配除了'\n'之外的任何字元0次或無限次,在DOTALL中也可以匹配'\n',執行後發現這裡就可以
	allpages_url_lst = [baseUrl + '/' + str(page_num) for page_num in range(1, pageNum+1)] # 這裡左閉右開,所以總的網頁數要+1
	return allpages_url_lst, listTotal

# 獲取總體資訊
def get_general_info(soup):
	general_info = soup.find('div', {'class': 'data-info d-flex item-tiling'})
	dl_info = general_info.find_all('dl')
	# print(info)
	self_article_num = dl_info[0]['title'] # 原創文章數量
	fans_num = dl_info[1]['title'] # 粉絲數
	like_num = dl_info[2]['title'] # 喜歡數
	comment_num = dl_info[3]['title'] # 評論數
	# print(self_article_num, fans_num, like_num, comment_num)
	return self_article_num, fans_num, like_num, comment_num

# 獲取等級資訊
def get_grade_info(soup):
	grade_info = soup.find('div', {'class': 'grade-box clearfix'})
	# print(grade_info)
	dd_info = grade_info.find_all('dd')
	grade = dd_info[0].a['title'].split(r',')[0] # 等級
	total_read_num = dd_info[1]['title'] # 總閱讀量
	earn_points = dd_info[2]['title'] # 積分
	rank = grade_info.find_all('dl')[-1]['title'] # 排名
	return grade, total_read_num, earn_points, rank

if __name__ == '__main__':
	now_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
	record_time = str(now_time) # 將作為資料存檔時間來儲存
	date_today = datetime.datetime.now().date() # 今天的時間,將作為文章索引
	# print(date_today, record_time)

	URL = "https://blog.csdn.net/liuchengzimozigreat" # 我的blog主頁(首頁/第一頁)

	# find list of image holder
	html = requests.get(URL).text
	soup = BeautifulSoup(html, 'lxml')

	# 提取總體資訊
	self_article_num, fans_num, like_num, comment_num = get_general_info(soup)

	# 提取等級資訊
	grade, total_read_num, earn_points, rank = get_grade_info(soup)
	

	# 儲存總體資訊
	file_path = r'H:\learning like never feel tired\Scraping python\my_blog_info' # 儲存位置
	general_info_file = file_path + '\\AAA_general_info.csv' # 前面加三個A好讓總體資訊出現在資料夾第一的位置
	if os.path.exists(general_info_file):
		df = pd.read_csv(general_info_file, index_col=0, engine='python', encoding='utf_8_sig', parse_dates=[0])
	else:
		df = pd.DataFrame(columns=('self_article_num', 'fans_num', 'like_num', 'comment_num', 'grade', 'total_read_num', 'earn_points', 'rank', 'record_time'))
	df.loc[date_today] = [self_article_num, fans_num, like_num, comment_num, grade, total_read_num, earn_points, rank, record_time] # 用當天日期作為索引,更新資訊
	df.to_csv(general_info_file, encoding='utf_8_sig')


	# 提取各個文章的資訊
	script_lst = soup.find_all('script') # 提取其中的script標籤tag,其中script_lst[-9]含有listtotal和pagesize資訊
	allpages_url_lst, total_article_num = get_allpages_url(str(script_lst[-9])) # 提取各page的url

	page_article_lst = [] # 儲存所有網頁中article集合的資訊
	for page_url in allpages_url_lst:
		html = requests.get(page_url).text
		soup = BeautifulSoup(html, 'lxml')
		article_info = soup.find_all('div', {'class': 'article-item-box csdn-tracking-statistics'})
		page_article_lst.append(article_info)
		# print(type(article_info))

	article_info_lst = [] # 將所有文章整合進同一個列表中
	for elem in page_article_lst:
		mark = 0
		for article in elem:
			if mark == 0: # 檢視HTML發現每一頁中都會在最開頭的地方多一篇未顯示的文章'帝都的凜冬'
				mark = 1
				continue
			article_info_lst.append(article)

	# 提取文章屬性並儲存之,將儲存八個資訊:文章資訊記錄的日期——作為索引、文章名、文章型別、建立時間、閱讀量、評論量、文章url、該條記錄時間(精確到秒)
	for article in article_info_lst:
		# print(article)
		span = article.find_all('span') # span包含了四個屬性,依次是:article_type, date, read_num, comment_num
		article_type = re.split(r'\s*', str(span[0]))[3] # 文章型別
		create_date = span[1].get_text() # 建立時間
		read_num = span[2].get_text() # 閱讀數
		comment_num = span[3].get_text() # 評論數

		article_url = article.a['href'] # 文章連結
		# 用'\n'分割結果類似:['', '    原    ', '    linux命令學習彙總      '],需將最後一個字串左右兩邊空格去掉才是我們想要的結果
		# 用' {2,}'——空格2到無限次分割結果類似:['', 'rails官方指南--建一個簡易部落格', ''],因此去列表中的第二個作為我們的article_name
		article_name = re.split(r' {2,}', re.split(r'\n*', str(article.find('a').get_text()))[-1])[1] # 文章名稱  當時我還不知道,其實換成re.sub()更好

		file_name = re.sub(r'[\s\':,\.()]*', '', article_name)
		file = file_path + '\\' + re.findall(r'\w{2,20}', file_name)[0] + '.csv' # 檔名不宜太長,所以最多取20個\w——單詞字元[A-Za-z0-9],過短又會衝突
		if os.path.exists(file):
			print('old article:', file)
			df = pd.read_csv(file, parse_dates=[0], index_col=0, engine='python', encoding='utf_8_sig') # 有時難免程式在一天內多次執行,讀入資料是保證一天只記錄一個數據
		else:
			print('NEW ARTICLE:', article_name)
			df = pd.DataFrame(columns=('article_name', 'article_type', 'create_date', 'read_num', 'comment_num', 'article_url', 'record_time'))
		# print(date_today)
		df.loc[date_today] = [article_name, article_type, create_date, read_num, comment_num, article_url, record_time] # 用當天日期作為索引,更新資訊
		df.to_csv(file, encoding='utf_8_sig')

# 下面是一些HTML目標模組的資訊,也就這些資訊,才能提煉出你想要的結果
'''
# 每個article的原始資訊
<div class="article-item-box csdn-tracking-statistics" data-articleid="78773093">
<h4 class="">
<a href="https://blog.csdn.net/liuchengzimozigreat/article/details/78773093" target="_blank">
<span class="article-type type-2">
            轉        </span>
        postgresql資料庫常用操作命令及SQL語言      </a>
</h4>
<p class="content">
<a href="https://blog.csdn.net/liuchengzimozigreat/article/details/78773093" target="_blank">
        環境ubuntu,安裝了postgresql
截圖命令:shift+PrtSc可以有十字游標,任選截圖區域
               alt+PrtSc擷取當前活動視窗
               PrtSc擷取整個螢幕

1postgresql常用操作:
(1)登入
[email protected]
</a> </p> <div class="info-box d-flex align-content-center"> <p> <span class="date">2017-12-11 16:10:49</span> </p> <p> <span class="read-num">閱讀數:914</span> </p> <p> <span class="read-num">評論數:0</span> </p> </div> </div> ''' ''' # general_info的資訊,包含原創、粉絲、喜歡、評論 <div class="data-info d-flex item-tiling"> <dl class="text-center" title="22"> <dt><a href="https://blog.csdn.net/liuchengzimozigreat?t=1">原創</a></dt> <dd><a href="https://blog.csdn.net/liuchengzimozigreat?t=1"><span class="count">22</span></a></dd> </dl> <dl class="text-center" id="fanBox" title="2"> <dt>粉絲</dt> <dd><span class="count" id="fan">2</span></dd> </dl> <dl class="text-center" title="5"> <dt>喜歡</dt> <dd><span class="count">5</span></dd> </dl> <dl class="text-center" title="4"> <dt>評論</dt> <dd><span class="count">4</span></dd> </dl> </div> ''' ''' <div class="grade-box clearfix"> <dl> <dt>等級:</dt> <dd> <a href="https://blog.csdn.net/home/help.html#level" target="_blank" title="2級,點選檢視等級說明"> <svg aria-hidden="true" class="icon icon-level"> <use xlink:href="#csdnc-bloglevel-2"></use> </svg> </a> </dd> </dl> <dl> <dt>訪問:</dt> <dd title="7462"> 7462 </dd> </dl> <dl> <dt>積分:</dt> <dd title="304"> 304 </dd> </dl> <dl title="289473"> <dt>排名:</dt> <dd>28萬+</dd> </dl> </div> '''