多線程爬蟲
先回顧前面學過的一些知識
1.一個cpu一次只能執行一個任務,多個cpu同時可以執行多個任務
2.一個cpu一次只能執行一個進程,其它進程處於非運行狀態
3.進程里包含的執行單元叫線程,一個進程可以包含多個線程
4.一個進程的內存空間是共享的,每個進程里的線程都可以使用這個共享空間
5.一個線程在使用這個共享空間的時候,其它的線程必須等待(阻塞狀態)
6.互斥鎖作用就是防止多個線程同時使用這塊內存空間,先使用的線程會將空間上鎖,其它的線程處於等待狀態。等鎖開了才能進
7.進程:表示程序的一次執行
8.線程:CPU運算的基本調度單位
9.GIL(全局鎖):python里的執行通行證,而且只有一個。拿到通行證的線程就可以進入CPU執行任務。沒有GIL的線程就不能執行任務
10.python的多線程適用於大量密集的I/O處理
11.python的多進程適用於大量的密集並行計算
多線程爬取糗事百科
#!/usr/bin/env python # -*- coding:utf-8 -*- # 使用了線程庫 import threading # 隊列 from Queue import Queue # 解析庫 from lxml import etree # 請求處理 import requests # json處理 import json import time class ThreadCrawl(threading.Thread): def __init__(self, threadName, pageQueue, dataQueue): #threading.Thread.__init__(self) # 調用父類初始化方法 super(ThreadCrawl, self).__init__() # 線程名 self.threadName = threadName # 頁碼隊列 self.pageQueue = pageQueue # 數據隊列 self.dataQueue = dataQueue # 請求報頭 self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'} def run(self): print "啟動 " + self.threadName while not CRAWL_EXIT: try: # 取出一個數字,先進先出 # 可選參數block,默認值為True #1. 如果對列為空,block為True的話,不會結束,會進入阻塞狀態,直到隊列有新的數據 #2. 如果隊列為空,block為False的話,就彈出一個Queue.empty()異常, page = self.pageQueue.get(False) url = "http://www.qiushibaike.com/8hr/page/" + str(page) +"/" #print url content = requests.get(url, headers = self.headers).text time.sleep(1) self.dataQueue.put(content) #print len(content) except: pass print "結束 " + self.threadName class ThreadParse(threading.Thread): def __init__(self, threadName, dataQueue, filename, lock): super(ThreadParse, self).__init__() # 線程名 self.threadName = threadName # 數據隊列 self.dataQueue = dataQueue # 保存解析后數據的文件名 self.filename = filename # 鎖 self.lock = lock def run(self): print "啟動" + self.threadName while not PARSE_EXIT: try: html = self.dataQueue.get(False) self.parse(html) except: pass print "退出" + self.threadName def parse(self, html): # 解析為HTML DOM html = etree.HTML(html) node_list = html.xpath('//div[contains(@id, "qiushi_tag")]') for node in node_list: # xpath返回的列表,這個列表就這一個參數,用索引方式取出來,用戶名 username = node.xpath('./div/a/@title')[0] # 圖片連接 image = node.xpath('.//div[@class="thumb"]//@src')#[0] # 取出標簽下的內容,段子內容 content = node.xpath('.//div[@class="content"]/span')[0].text # 取出標簽里包含的內容,點贊 zan = node.xpath('.//i')[0].text # 評論 comments = node.xpath('.//i')[1].text items = { "username" : username, "image" : image, "content" : content, "zan" : zan, "comments" : comments } # with 后面有兩個必須執行的操作:__enter__ 和 _exit__ # 不管里面的操作結果如何,都會執行打開、關閉 # 打開鎖、處理內容、釋放鎖 with self.lock: # 寫入存儲的解析后的數據 self.filename.write(json.dumps(items, ensure_ascii = False).encode("utf-8") + "\n") CRAWL_EXIT = False PARSE_EXIT = False def main(): # 頁碼的隊列,表示20個頁面 pageQueue = Queue(20) # 放入1~10的數字,先進先出 for i in range(1, 21): pageQueue.put(i) # 采集結果(每頁的HTML源碼)的數據隊列,參數為空表示不限制 dataQueue = Queue() filename = open("duanzi.json", "a") # 創建鎖 lock = threading.Lock() # 三個采集線程的名字 crawlList = ["采集線程1號", "采集線程2號", "采集線程3號"] # 存儲三個采集線程的列表集合 threadcrawl = [] for threadName in crawlList: thread = ThreadCrawl(threadName, pageQueue, dataQueue) thread.start() threadcrawl.append(thread) # 三個解析線程的名字 parseList = ["解析線程1號","解析線程2號","解析線程3號"] # 存儲三個解析線程 threadparse = [] for threadName in parseList: thread = ThreadParse(threadName, dataQueue, filename, lock) thread.start() threadparse.append(thread) # 等待pageQueue隊列為空,也就是等待之前的操作執行完畢 while not pageQueue.empty(): pass # 如果pageQueue為空,采集線程退出循環 global CRAWL_EXIT CRAWL_EXIT = True print "pageQueue為空" for thread in threadcrawl: thread.join() print "1" while not dataQueue.empty(): pass global PARSE_EXIT PARSE_EXIT = True for thread in threadparse: thread.join() print "2" with lock: # 關閉文件 filename.close() print "謝謝使用!" if __name__ == "__main__": main()