簡單的實現一個python3的多線程爬蟲,爬取p站上的每日熱門排行榜,python3多線程

來源:互聯網
上載者:User

簡單的實現一個python3的多線程爬蟲,爬取p站上的每日熱門排行榜,python3多線程

  大概半年前我開始學習python,也就是半年前,我半抄半改的同樣的爬蟲寫了出來,由於是單線程的程式,當中出了一點的小錯就會崩潰,但是那個爬蟲中的header之類的東西現在依舊還是能夠使用的,於是我就把之前那份的保留了下來。由於有一半是抄的,自己得到的並不多,這次重寫,我相當於又重新學習了一遍。當中有可能有認識不足的,歡迎指正。

  首先我們要想登陸p站,得構造一個請求,p站登陸的請求包括:

request = urllib.request.Request(    #建立請求    url=login_url, #連結    data=login_data, #資料    headers=login_header #頭)

url通過猜測就可以得到是https://www.pixiv.net/login.php,但是由於採用了https加密data和headers卻不好獲得,這裡我採用了之前的那份的,沒想到還能用:

data = {    #構建請求資料    "pixiv_id": self.id, #帳號    "pass": self.passwd, #密碼                                                                            "mode": "login",    "skip": 1}
login_header = {    #構建要求標頭    "accept-language": "zh-cn,zh;q=0.8",    "referer": "https://www.pixiv.net/login.php?return_to=0",    "user-agent": "mozilla/5.0 (windows nt 10.0; win64; x64; rv:45.0) gecko/20100101 firefox/45.0"}

  因為是要爬取多個頁面的圖,我這裡採用cookie登陸的方式,不過因為可能cookie會變每次運行還得重新登陸:

cookie = http.cookiejar.MozillaCookieJar(".cookie") #建立cookie每次都覆蓋,進行更新handler = urllib.request.HTTPCookieProcessor(cookie)opener = urllib.request.build_opener(handler)response = opener.open(request)print("Log in successfully!")cookie.save(ignore_discard=True, ignore_expires=True)response.close()print("Update cookies successfully!")

  登陸解決了,cookie登陸其實是很簡單的,只要載入本地的cookie檔案就行了:

def cookie_opener(self):    #使用cookie登陸, 建立opener    cookie = http.cookiejar.MozillaCookieJar()    cookie.load(".cookie", ignore_discard=True, ignore_expires=True)    handler = urllib.request.HTTPCookieProcessor(cookie)    opener = urllib.request.build_opener(handler)    return opener

一開始我的想法是先將所有的連結中的圖片連結解析出來,然後再下載,在統籌學看來這樣的做法就是完全的浪費時間的,因為解析和下載所用的時間是不一樣的,解析可能會花上3,4分鐘,而單獨的下載只要10秒以內。在電腦允許的情況下,一個線程專門負責解析,另外的線程專門負責下載,效率會非常的高。

後來通過學習,我改成了生產者消費者模式:

1. 一個Crawler爬取連結中的圖片連結,放入處理隊列中

2. n個Downloader下載爬出到的圖片

本文的生產者和消費者模式

可以做到邊解析,邊下載,由於通常解析的速度是快於下載的速度的,一開始可能下載的速度是快過解析的,但是後來會被反超,採用一個解析器對多個下載器的模式效率並沒有多小的差別。

具體的實現,我是將downloader作為threading.Thread的一個衍生類別:

class downloader (threading.Thread):    #將一個下載器作為一個線程    def __init__(self, q, path, opener):        threading.Thread.__init__(self)        self.opener = opener     #下載器用的opener        self.q = q #主隊列        self.sch = 0    #進度[0-50]        self.is_working = False    #是否正在工作        self.filename = ""    #當前下載的檔案名稱        self.path = path #檔案路徑        self.exitflag = False #是否退出的訊號    def run(self):        def report(blocks, blocksize, total):    #回呼函數用於更新下載的進度            self.sch = int(blocks * blocksize / total * 50)    #計算當前下載百分比            self.sch = min(self.sch, 50)    #忽略溢出        def download(url, referer, path):    #使用urlretrieve下載圖片            self.opener.addheaders = [    #給opener添加一個頭                ('Accept-Language', 'zh-CN,zh;q=0.8'),                ('User-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:45.0) Gecko/20100101 Firefox/45.0'),                ('Referer', referer)    #p站的防盜鏈機制            ]            pattern = re.compile(r'([a-zA-Z.0-9_-]*?)$')    #正則匹配處理模式            filename = re.search(pattern, url).group(0)    #匹配圖片連結產生本地檔案名稱            if filename.find("master") != -1:            #去除多圖的master_xxxx的字串                master = re.search(re.compile(r'_master[0-9]*'), filename)                filename = filename.replace(master.group(0), '')            self.filename = filename            urllib.request.install_opener(self.opener) #添加更新後的opener            try:                urllib.request.urlretrieve(url, path + filename, report) #下載檔案到本地            except:                os.remove(path + filename) #如果下載失敗,將問題檔案刪除,並將referer和url重新放入隊列                self.q.put((referer, url))        while not self.exitflag:            if not self.q.empty(): #當隊列非空擷取隊列首部元素,開始下載                links = self.q.get()                self.is_working = True                download(links[1], links[0], self.path)                self.sch = 0  #置零                self.is_working = False

封裝的downloader類作為一個單獨的線程起到了和threading.Thread一樣的作用,同時對下載器任務的一些說明,可以在後面的啟動並執行過程中顯示各個下載器的進度。

爬取地址的時候,先把熱門排行榜首頁的掃一遍,擷取所有作品的地址,以下都用到了beautifulsoup模組:

response = opener.open(self.url)html = response.read().decode("gbk", "ignore")    #編碼,忽略錯誤(錯誤一般不存在在連結上)soup = BeautifulSoup(html, "html5lib")    #使用bs和html5lib解析器,建立bs對象tag_a = soup.find_all("a")for link in tag_a:    top_link = str(link.get("href"))    #找到所有<a>標籤下的連結    if top_link.find("member_illust") != -1:        pattern = re.compile(r'id=[0-9]*')    #過濾存在id的連結        result = re.search(pattern, top_link)        if result != None:            result_id = result.group(0)            url_work = "http://www.pixiv.net/member_illust.php?mode=medium&illust_" + result_id            if url_work not in self.rankurl_list:                self.rankurl_list.append(url_work)

解析由於只有一個線程,所以我就用了一般的用法:

def _crawl():  while len(self.rankurl_list) > 0:    url = self.rankurl_list[0]    response = opener.open(url)    html = response.read().decode("gbk", "ignore")    #編碼,忽略錯誤(錯誤一般不存在在連結上)    soup = BeautifulSoup(html, "html5lib")    imgs = soup.find_all("img", "original-image")    if len(imgs) > 0:      self.picurl_queue.put((url, str(imgs[0]["data-src"])))      else:      multiple = soup.find_all("a", " _work multiple ")      if len(multiple) > 0:        manga_url = "http://www.pixiv.net/" + multiple[0]["href"]        response = opener.open(manga_url)        html = response.read().decode("gbk", "ignore")        soup = BeautifulSoup(html, "html5lib")        imgs = soup.find_all("img", "image ui-scroll-view")        for i in range(0, len(imgs)):          self.picurl_queue.put((manga_url + "&page=" + str(i), str(imgs[i]["data-src"])))    self.rankurl_list = self.rankurl_list[1:]  self.crawler = threading.Thread(target=_crawl) #開第一個線程用於爬取連結,生產者消費者模式中的生產者  self.crawler.start()

與此同時產生和之前所設的最大線程數相等的線程:

for i in range(0, self.max_dlthread): #根據設定的最大線程數開闢下載線程,生產者消費者模式中的消費者    thread = downloader(self.picurl_queue, self.os_path, opener)    thread.start()    self.downlist.append(thread) #將產生的線程放入一個隊列中

接下來就是顯示多線程每一個線程的下載進度,同時等待所有的事情處理結束了:

flag = Falsewhile not self.picurl_queue.empty() or len(self.rankurl_list) > 0 or not flag:#顯示進度,同時等待所有的線程結束,結束的條件(這裡取相反):#1 下載隊列為空白#2 解析列表為空白#3 當前所有的下載任務完成    os.system("cls")    flag = True    if len(self.rankurl_list) > 0:        print(str(len(self.rankurl_list)) + " urls to parse...")    if not self.picurl_queue.empty():        print(str(self.picurl_queue.qsize()) + " pics ready to download...")    for t in self.downlist:        if t.is_working:            flag = False            print("Downloading " + '"' + t.filename + '" : \t[' + ">"*t.sch + " "*(50-t.sch) + "] " + str(t.sch*2) + " %")        else:            print("This downloader is not working now.")    time.sleep(0.1)

下面是實際啟動並執行圖:

多線程,每個線程的進度都不同,上面顯示的分別是需要解析的連結和已經準備好要下載的連結

等到所有的任務結束,給所有的線程發送一個退出指令:

for t in self.downlist: #結束後給每一個下載器發送一個退出指令  t.exitflag = True

等所有任務結束,系統將會給出下載所用的時間:

def start(self):    st = time.time()    self.login()    opener = self.cookie_opener()    self.crawl(opener)    ed = time.time()    tot = ed - st    intvl = getTime(int(tot))    os.system("cls")    print("Finished.")    print("Total using " + intvl  + " .") #統計全部工作結束所用的時間

2016年12月14日,p站每日榜全部爬取所用的時間:

下面給出coding上的地址:

https://coding.net/u/MZI/p/PixivSpider/git

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.