正所謂否副,民生無小事,今日多關(guān)注曲尸,今天我們利用多線程來爬取陽光問政,關(guān)注一下老百姓需要解決什么問題鸦列。
線程
什么是線程
線程是輕量級進程,是操作系統(tǒng)能夠進行運算調(diào)度的最小單位,它被包涵在進程之中诲锹,是進程中的實際運作單位。
其生命周期可以分為五個狀態(tài)——新建捻浦、就緒、運行盗扒、阻塞、終止缕碎,如下圖所示:
新建狀態(tài):新創(chuàng)建的線程在調(diào)用 start() 方法之前赊抖,不會得到執(zhí)行檐薯;
就緒狀態(tài):新建狀態(tài)的線程調(diào)用 start() 方法后,該線程就轉(zhuǎn)換到就緒狀態(tài),當獲取到CPU資源就可以執(zhí)行;
運行狀態(tài):就緒狀態(tài)的線程得到了 CPU資源举户,并開始執(zhí)行 target 參數(shù)執(zhí)行的目標函數(shù)或者 run() 方法;
阻塞狀態(tài):當 CPU 對多個線程進行調(diào)度時拐云,對于獲得 CPU 調(diào)度卻沒有執(zhí)行完畢的線程,該線程就進入阻塞狀態(tài)薇缅;
終止狀態(tài):線程執(zhí)行結(jié)束、發(fā)生異常(Exception)或錯誤(Error)泻骤,線程就會進入終止狀態(tài)演痒。
線程創(chuàng)建
創(chuàng)建線程可以分為五步:編寫執(zhí)行程序、創(chuàng)建線程類讯嫂、在線程類run方法中調(diào)用要執(zhí)行的程序、開啟線程和等待線程結(jié)束。
編寫執(zhí)行程序
import time
def print_time(threadName):
for i in range(5):
time.sleep(0.5)
timestamp = time.time()
date = time.localtime(timestamp)
Now_date = time.strftime('%Y-%m-%d %H:%M:%S', date)
print ("%s: %s" % (threadName, Now_date))
在執(zhí)行程序中曲楚,我們通過time來獲取當前系統(tǒng)時間,由于程序執(zhí)行速度是很快的讯柔,所以我們通過time.sleep()方法讓程序休眠0.5秒,這樣我們就可以看到線程的交替執(zhí)行捣炬。
創(chuàng)建線程類
import threading
class myThread (threading.Thread):
def __init__(self, name):
super(myThread,self).__init__()
self.name = name
def run(self):
pass
首先導入線程模塊threading灭美,創(chuàng)建myThread()線程類并通過threading.Thread來繼承線程屬性铁坎,調(diào)用super()方法并初始化name變量硬萍。
run()方法
def run(self):
print ("開始線程:" + self.name)
print_time(self.name)
print ("退出線程:" + self.name)
在run方法中,我們通過調(diào)用print_time()方法并傳入self.name參數(shù)來執(zhí)行第一步編寫的執(zhí)行程序。
開啟、等待線程
線程類和執(zhí)行程序都寫好了婉徘,接下來開啟線程并等待線程結(jié)束儒鹿,具體代碼如下所示:
thread1 = myThread("Thread-1")
thread2 = myThread("Thread-2")
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主線程")
這里我們創(chuàng)建了兩個線程,首先通過實例化線程類myThread()并傳入線程名,調(diào)用start()方法使線程處于就緒狀態(tài)喷鸽,再調(diào)用join()方法等待線程結(jié)束。
運行結(jié)果如下所示:
爬前分析
首先進入陽光問政最新問政網(wǎng)頁并打開開發(fā)者模式,如下圖所示:
這個網(wǎng)站比較簡單,網(wǎng)頁的URL鏈接最后一個數(shù)字就是它的頁碼,所以我們構(gòu)造url鏈接時,可以這樣:
for i in range(1,6):
url=f'http://wzzdg.sun0769.com/political/index/politicsNewest?id=1&page={i}'
這樣就可以獲取多頁數(shù)據(jù)了,在源代碼中也有我們想要的詳情網(wǎng)頁url鏈接洪乍,其存放在上圖的右邊紅框中。
隨機打開一個問政問題并打開開發(fā)者模式巷波,如下圖所示:
可以發(fā)現(xiàn)該url為http://wzzdg.sun0769.com/political/politics/index?id=529559荤傲,對比上上圖的詳情網(wǎng)頁url终佛,只需要在獲取到的url鏈接前面添加http://wzzdg.sun0769即可绍豁。
詳情網(wǎng)頁也很簡單,我們想要的數(shù)據(jù)在源代碼中也有,所以我們待會只要獲取URL鏈接頁面的源代碼即可獲取到所有數(shù)據(jù)晶衷。
實戰(zhàn)演練
在實戰(zhàn)演練中,我們首先通過編寫單線程爬蟲來爬取陽光問政的數(shù)據(jù)锹漱,再通過多線程程序執(zhí)行單線程爬蟲。
單線程爬蟲
獲取詳情網(wǎng)頁url
首先獲取詳情網(wǎng)頁的url鏈接嗅辣,主要代碼如下所示:
import requests
import parsel
import pymysql
def get_link():
for i in range(1,6):
url=f'http://wzzdg.sun0769.com/political/index/politicsNewest?id=1&page={i}'
response=requests.get(url,headers=headers)
Xapth=parsel.Selector(response.text)
f = open('url.txt', 'a', encoding='utf-8')
ul_list = Xapth.xpath('//html/body/div[2]/div[3]/ul[2]/li')
for li in ul_list:
url_).extract_first()
f.write(url_href)
f.write('\n')
get_data(url_href)
我們一共獲取5頁數(shù)據(jù),每頁數(shù)據(jù)有15條詳情網(wǎng)頁的URL鏈接蛙奖,通過requests.get()方法發(fā)出網(wǎng)絡(luò)請求,并通過parsel.Selector()方法來解析響應(yīng)的文本數(shù)據(jù),最后將獲取到的url鏈接傳入到自定義get_data()方法。
注意:這里我們把url鏈接保存在一個txt文本中,方便我們在多線程里使用所有詳情網(wǎng)頁的url鏈接蝎抽。
獲取詳情網(wǎng)頁數(shù)據(jù)
獲取詳情網(wǎng)頁的url后,接下來就獲取其內(nèi)容了,具體代碼如下所示:
def get_data(i):
response=requests.get(i,headers=headers)
Xapth=parsel.Selector(response.text)
data={}
data['number_id']=Xapth.xpath('/html/body/div[3]/div[2]/div[2]/div[1]/span[4]/text()').extract_first().replace('編號:','')
data['state_now']=Xapth.xpath('/html/body/div[3]/div[2]/div[2]/div[1]/span[3]/text()').extract_first().replace('狀態(tài):','').strip()
data['PoliticalTitle']=Xapth.xpath('/html/body/div[3]/div[2]/div[2]/p/text()').extract_first()
data['PoliticalTime']=Xapth.xpath('/html/body/div[3]/div[2]/div[2]/div[1]/span[2]/text()').extract_first().replace('發(fā)布日期','')
data['url_href']=i
data['text']=Xapth.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first().replace('\n','').replace('\r','')
saving_scenery_data(list(data.values()))
獲取詳情網(wǎng)頁的數(shù)據(jù)和獲取url鏈接代碼差不多,這里我就不一一解釋了玫镐,最后我們把數(shù)據(jù)傳遞到自定義方法saving_sunshine_data()中傍念。
保存數(shù)據(jù)
這次數(shù)據(jù)我們保存在MySQL數(shù)據(jù)庫中,主要代碼如下圖所示:
def saving_scenery_data(srr):
db = pymysql.connect(host=host, user=user, password=passwd, port=port, db='Politics')
cursor = db.cursor()
sql = 'insert into problem_data(number_id, state_now, PoliticalTitle, PoliticalTime, url_href,text) values(%s,%s,%s,%s,%s,%s)'
try:
cursor.execute(sql,srr)
db.commit()
except:
db.rollback()
db.close()
首先連接數(shù)據(jù)庫,通過cursor()方法獲取游標驳概,再通過.execute()方法執(zhí)行單條的sql語句,執(zhí)行成功后返回受影響的行數(shù)稚照,然后關(guān)閉數(shù)據(jù)庫連接。當保存的數(shù)據(jù)不成功弱恒,就調(diào)用rollback()方法锈玉,撤消當前事務(wù)中所做的所有更改,并釋放此連接對象當前使用的任何數(shù)據(jù)庫鎖。
啟動程序
好了穷蛹,主要代碼已經(jīng)寫好了,接下來編寫啟動程序的代碼,主要代碼如下圖所示:
if __name__ == '__main__':
t1=time.time()
get_link()
t2=time.time()
print(t2-t1)
這里我們通過time.time()方法來獲取爬蟲程序的執(zhí)行時間励烦。
運行結(jié)果如下圖所示:
從結(jié)果來看,單線程爬取數(shù)據(jù)用了16秒,接下來編寫多線程來爬取數(shù)據(jù)友多。
多線程爬蟲
剛才單線程爬蟲的文件名為yangguang.py纵柿,可以直接調(diào)用單線程爬蟲方法來編寫多線程爬蟲酬土,首先創(chuàng)建多線程爬蟲類撤缴,主要代碼如下所示:
import yangguang
import threading
import time
f=open('url.txt',mode='r')
class mythread(threading.Thread):
def __init__(self,f):
super(mythread,self).__init__()
self.f=f
def run(self)->None:
for i in self.f:
yangguang.get_data(i)
首先導入單線程爬蟲yangguang.py文件,打開剛才單線程爬蟲保存的txt文件,再創(chuàng)建mythread()類并初始化線程類岳守,重寫run()方法,通過for循環(huán)把txt文件中的url讀取并傳遞在單線程爬蟲yangguang.get_data()方法中譬重。
好了,多線程類寫好了,接下來編寫執(zhí)行代碼,主要代碼如下所示:
if __name__ == '__main__':
t1=time.time()
yangguang.create_db()
threads = [mythread(f) for i in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
t2=time.time()
print(t2-t1)
這里我們創(chuàng)建了10個線程,運行結(jié)果如下所示:
開啟十個線程來爬取數(shù)據(jù),一共用了2.4秒萎胰,大大提高了爬蟲效率。
好了,多線程爬取陽光問政就講到這里了搓扯,感謝觀看!;豢伞!