Python爬蟲基礎 | 多線程編程及多線程爬取京東手機信息

Python Multithreaded
引言

在多線程編程出現之前,電腦程序的運行由一個執行序列組成,執行序列按順序在主機的CPU中運行。無論是任務本身要求順序執行還是整個程序是由多個子任務組成,程序都是按這種方式執行的。即使子任務相當獨立,相互無關(即,一個子任務的結果不影響其他子任務的結果)。這樣并行處理可以大幅度地提升整個任務的效率,這也就是多線程編程的目的。

什么是線程

線程(有時被稱為輕量級進程)跟進程有些相似,不同的是,所有的線程運行在同一個進程,共享相同的運行環境。
線程有開始,順序執行和結束三部分。它有自己的指令指針,記錄自己運行到什么地方。線程的運行可能被搶占(中斷),或暫時掛起(也叫睡眠),讓其他的線程運行(也叫讓步)。一個進程中的各個進程之前共享同一片數據空間,所以線程之間可以比進程之間更方便的共享數據和之間的互相通訊。

全局解釋器鎖(GIL)

Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到在主循環中,同時只有一個線程在運行,就像在單CPU的系統中運行多個進程那樣,盡管在內存中可以存放多個程序,但是在任意時刻,只有一個程序在CPU中運行。
對于Python虛擬機的訪問由GIL來控制,正是這個鎖能保證同一時刻只有一個線程在運行。在多線程的環境中,Python虛擬機按一下方式運行:

  • 設置GIL
  • 切換到一個線程中運行
  • 運行: a. 指定數量的字節碼的指令,或者線程主動讓出控制(可以調用time.sleep(0))
  • 把線程設置為睡眠狀態
  • 解鎖GIL
  • 重復以上所用步驟
    編寫擴展程序的程序員可以主動解鎖GIL,不過Python的開發人員則不用擔心在這個情況下你的Python代碼會被鎖住。
    例如,對所有面向I/O的程序來說,GIL會在這個I/O調用之前被釋放,以允許其他的線程在這個線程等待I/O的時候允許。如何某線程并未使用很多I/O操作,它會在自己的時間片內一直占用CPU和GIL,也就是說,I/O密集型的Python程序比計算密集型的程序更能充分利用多線程環境的好處
沒有線程支持的情況

示例1
在單線程中順序執行兩個循環。一個循環結束后,另一個才能開始。總的時間是各個循環運行的時間之和。

#!/usr/bin/env python

from  time import sleep, ctime

def loop0():
    print 'start loop0 at: ', ctime()
    sleep(4)
    print 'loop0 done at: ', ctime()

def loop1():
    print 'start loop1 at: ', ctime()
    sleep(2)
    print 'loop1 done at: ', ctime()

def main():
    print 'starting at: ', ctime()
    loop0()
    loop1()
    print 'all Done at: ', ctime()

if __name__ == '__main__':
    main()

執行結果:loop0和loop1各自運行了4秒和2秒,整個程序耗時6秒,屬于串行執行

運行結果

Python的threading模塊

Python提供了幾個用于多線程編程的模塊,包括threadthreadingQueue等。

  • thread模塊提供了基本的線程和鎖的支持
  • threading提供了更高級、功能更強的線程管理的功能
  • Queue模塊允許用戶創建一個可以用于多個線程之間共享數據的隊列數據結構。

示例2


#!/usr/bin/env python

import thread
from time import sleep, ctime

def loop0():
    print 'start loop 0 at: ', ctime()
    sleep(4)
    print 'loop 0 done at: ', ctime()

def loop1():
    print 'start loop1 at: ', ctime()
    sleep(2)
    print 'loop1 done at: ', ctime()

def main():
    print 'starting at: ', ctime()
    #start_new_thread()要求一定要有前兩個參數,就算運行的函數不需要傳參,也要傳一個空元組
    thread.start_new_thread(loop0,())
    thread.start_new_thread(loop1,())
    #睡6秒來確保2個子線程都運行結束,防止主線程過早退出
    sleep(6)
    print 'all Done at: ', ctime()

if __name__ == '__main__':
    main()

執行結果:loop0和loop1并行執行,2個函數執行耗時4秒


image.png

示例3

使用線程和鎖

引入鎖的概念是為了線程不用什么時候結束再做額外的等待,使用了鎖,我們就可以在兩個線程都退出后,馬上退出。(線程管理)

#!/usr/bin/env python

import thread
from time import sleep, ctime

loops = [4, 2]


def loop(nloop, nsec, lock):
    print 'start loop', nloop, 'at: ',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at: ',ctime()
    lock.release()


def main():
    print 'starting at: ', ctime()
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate_lock()
        lock.acquire()
        locks.append(lock)

    for i in nloops:
        thread.start_new_thread(loop, (i, loops[i], locks[i]))

    for i in nloops:
        while locks[i].locked():
            pass

    print 'all Done at: ', ctime()


if __name__ == '__main__':
    main()
    

執行結果:

image.png

示例4
使用threading模塊的Thread類的一個join(),允許主線程等待子線程的結束

#!/usr/bin/env python

import  threading
from time import sleep, ctime

loops = [4,2]

def loop(nloop, nsec):
    print 'start loop', nloop, 'at: ',ctime()
    sleep(nsec)
    print 'loop',nloop,'done at: ',ctime()

def main():
    print 'starting at: ', ctime()
    threads = []
    nloops = range(len(loops))
  
    for i in nloops:
        t = threading.Thread(target=loop, args=(i, loops[i]))
        threads.append(t)
    
    for i in nloops: #start thread
        threads[i].start()

    for i in nloops: #wait for all
        threads[i].join() #threads to finish

    print ' all Done at: ', ctime()

if __name__ == '__main__':
    main()

執行結果: 所有的線程先都創建好之后,再一起調用start()函數啟動,而不是創建一個啟動一個,而且不需要在管理一堆鎖(分配鎖、獲得鎖、釋放鎖、檢查鎖等),只需簡單地對每個線程調用join()函數就可以了。

image.png

示例5
線程池threadpool模塊

import time
from multiprocessing.dummy import Pool as ThreadPool
def printhello(str):
    print "Hello ", str
    time.sleep(2)

name_list =['world1', 'world2', 'world3', 'world4']
start_time = time.time()
pool = ThreadPool(10)
pool.map(printhello, name_list)
pool.close()
pool.join()

end_time = time.time()
print '%d second'% (end_time-start_time)

image.png

示例6
多線程爬取京東手機信息

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import time
import json
import requests
from lxml import etree
from pymongo import MongoClient
from multiprocessing.dummy import Pool as ThreadPool
reload(sys)
sys.path.append('..')
sys.setdefaultencoding('utf-8')



def request_html(url):
        html = requests.get(url, headers=headers)
        selector = etree.HTML(html.text)
        goodslist = selector.xpath('//ul[@class="gl-warp clearfix"]/li')
        for goods in goodslist:
            try:
                sku_id = goods.xpath('@data-sku')[0]
                comment_url = 'https://item.jd.com/{}.html'.format(str(sku_id))
                get_comment_data(comment_url)
            except Exception as e:
                print e

def get_comment_data(url):
    dict = {}
    html = requests.get(url, headers=headers)
    selector = etree.HTML(html.text)
    product_infos = selector.xpath('//ul[@class="parameter2 p-parameter-list"]')
    for product in product_infos:
        product_name = product.xpath('li[1]/@title')[0]
        product_number = product.xpath('li[2]/@title')[0]
        product_price = get_product_price(product_number)
        gross_weight = product.xpath('li[4]/@title')[0]
        commodity_origin = product.xpath('li[3]/@title')[0]
        dict["商品名稱"] = product_name
        dict["價格"] = product_price
        dict["商品編號"] = product_number
        dict["毛重"] = gross_weight
        dict["產地"]=commodity_origin
    save_to_mongodb(dict)

def get_product_price(sku): #直接xpath提取價格信息返回null,價格是js加載的
    url = "https://p.3.cn/prices/mgets?&skuIds=J_%s" %str(sku)
    html = requests.get(url, headers=headers).content
    html_json = json.loads(html)
    for info in html_json:
        return info.get('p')


def save_to_mongodb(list):
    client = mongoclient
    db = client['goods']
    posts = db.jd
    posts.insert(list)

if __name__ == '__main__':
    headers = {
            'Cookie': 'ipLoc-djd=1-72-2799-0; unpl=V2_ZzNtbRZXF0dwChEEfxtbV2IKFQ4RUBcSdg1PVSgZCVAyCkBVclRCFXMUR1NnGFkUZgoZXkpcQxNFCHZXchBYAWcCGllyBBNNIEwHDCRSBUE3XHxcFVUWF3RaTwEoSVoAYwtBDkZUFBYhW0IAKElVVTUFR21yVEMldQl2VH4RWAVmBxVeS19AEHUJR1x6GFsBYQEibUVncyVyDkBQehFsBFcCIh8WC0QcdQ1GUTYZWQ1jAxNZRVRKHXYNRlV6EV0EYAcUX3JWcxY%3d; __jdv=122270672|baidu-pinzhuan|t_288551095_baidupinzhuan|cpc|0f3d30c8dba7459bb52f2eb5eba8ac7d_0_e1ec43fa536c486bb6e62480b1ddd8c9|1496536177759; mt_xid=V2_52007VwMXWllYU14YShBUBmIDE1NVWVNdG08bbFZiURQBWgxaRkhKEQgZYgNFV0FRVFtIVUlbV2FTRgJcWVNcSHkaXQVhHxNVQVlXSx5BEl0DbAMaYl9oUmofSB9eB2YGElBtWFdcGA%3D%3D; __jda=122270672.14951056289241009006573.1495105629.1496491774.1496535400.5; __jdb=122270672.26.14951056289241009006573|5.1496535400; __jdc=122270672; 3AB9D23F7A4B3C9B=EJMY3ATK7HCS7VQQNJETFIMV7BZ5NCCCCSWL3UZVSJBDWJP3REWXTFXZ7O2CDKMGP6JJK7E5G4XXBH7UA32GN7EVRY; __jdu=14951056289241009006573',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
        }
    urls = ['https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%E6%89%8B%E6%9C%BA&cid2=653&cid3=655&page={}&click=0'.format(str(i)) for i in range(1, 200, 2)]
    mongoclient = MongoClient('mongodb://172.16.110.163:27017/')
    pool = ThreadPool(4)
    start = time.time()

    pool.map(request_html, urls)
    pool.close()
    pool.join()

    end = time.time()
    print 'Total Time:' + str(end - start) + 's'






image.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • 目錄 一、開啟線程的兩種方式 在python中開啟線程要導入threading,它與開啟進程所需要導入的模塊mul...
    CaiGuangyin閱讀 2,418評論 1 16
  • 線程 引言&動機 考慮一下這個場景,我們有10000條數據需要處理,處理每條數據需要花費1秒,但讀取數據只需要0....
    不浪漫的浪漫_ea03閱讀 371評論 0 0
  • 引言&動機 考慮一下這個場景,我們有10000條數據需要處理,處理每條數據需要花費1秒,但讀取數據只需要0.1秒,...
    chen_000閱讀 518評論 0 0
  • 前言:為什么有人說 Python 的多線程是雞肋,不是真正意義上的多線程? 看到這里,也許你會疑惑。這很正常,所以...
    猴哥愛讀書閱讀 51,712評論 6 69
  • 《傷寒論》桂枝甘草湯 桂枝12克、炙甘草6克。 原文:發汗過多,其人叉手自冒心,心下悸,欲得按者。 1、失眠者需要...
    秦小涵閱讀 231評論 0 0