python網(wǎng)絡(luò)爬蟲:多任務(wù)-進程、線程

一 、實現(xiàn)多任務(wù)的方式

多線程
多進程
協(xié)程
多線程+多進程

并行,并發(fā)

并行:同時發(fā)起同時執(zhí)行,(4核,4個任務(wù))
并發(fā):同時發(fā)起,單個執(zhí)行
在python語言中,并不能真正意義上實現(xiàn)多線程,
因為cpython解釋器有一個全局GIL解釋器鎖,來保證同一時刻只有一個線程在執(zhí)行

線程

線程:是cpu執(zhí)行的基本單元,占用資源少,并且線程和線程間的資源是共享的,線程依賴進程存在的,
多線程一般適用于I/O密集型操作,線程的執(zhí)行時無序的

  • 線程的創(chuàng)建使用
from threading import Thread
import threading,time

data = []

def download_image(url,num):
    '''
    下載圖片
    :param url:
    :param num:
    :return:
    '''
    global data
    time.sleep(2)
    print(url, num)
    data.append(num)
def read_data():
    global data
    for i in data:
        print(i)
if __name__ == '__main__':

    # 獲取當(dāng)前線程的名稱 threading.currentThread().name
    print('主線程開啟',threading.currentThread().name)
    # 創(chuàng)建一個子線程
    '''
    target=None,  線程要執(zhí)行的目標函數(shù)
    name=None, 創(chuàng)建線程時,指定線程的名稱
    args=(), 為目標函數(shù)傳參數(shù),(tuple元組類型)
    
    '''

    thread_sub1 = Thread(target=download_image,name='下載線程',args=('http://d.hiphotos.baidu.com/image/pic/item/9825bc315c6034a84d0cf125c6134954082376a3.jpg',1))
    thread_sub2 = Thread(target=read_data,name='讀取線程',)
    # 是否開啟守護進程(在開啟線程之前設(shè)置)
    # daemon:Flase,在主線程結(jié)束時,會檢測子線程任務(wù)是否結(jié)束,
    # 如果子線程中任務(wù)沒有結(jié)束,則會讓子線程正常結(jié)束任務(wù)
    # daemon:True ,在主線程結(jié)束時,會檢測子線程任務(wù)是否結(jié)束,
    # 如果子線程任務(wù)沒有結(jié)束,則會讓子線程跟隨主線程一起結(jié)束
    thread_sub1.daemon = True
    # 啟動線程
    thread_sub1.start()

    # join():阻塞,等待子線程中的人物執(zhí)行完畢后,再回到主線程中繼續(xù)執(zhí)行
    thread_sub1.join()
    # 開啟線程
    thread_sub2.start()
    thread_sub2.join()
    print('主線程結(jié)束', threading.currentThread().name)

隊列

Queue(隊列對象) Queue是python中的標準庫喻喳,可以直接import Queue引用;

隊列是線程間最常用的交換數(shù)據(jù)的形式

python下多線程的思考

對于資源邑遏,加鎖是個重要的環(huán)節(jié)佣赖。因為python原生的list,dict等,都是not thread safe的记盒。而Queue憎蛤,是線程安全的,因此在滿足使用條件下纪吮,建議使用隊列

1.初始化: class (FIFO 先進先出)

Queue.Queue(maxsize)
2.包中的常用方法:

Queue.qsize() 返回隊列的大小

Queue.empty() 如果隊列為空俩檬,返回True,反之False

Queue.full() 如果隊列滿了,返回True,反之False

Queue.full 與 maxsize 大小對應(yīng)

Queue.get(block,timeout)獲取隊列碾盟,timeout等待時間

創(chuàng)建一個“隊列”對象
import Queue


 maxsize:指定隊列中能夠存儲的最大數(shù)據(jù)量
 dataqueue = queue.Queue(maxsize=40)

 for i in range(0,40):
     if not dataqueue.full():
        dataqueue.put(i)
# 判斷隊列是否為空
isempty = dataqueue.empty()
print(isempty)

判斷隊列是否存滿了
isfull = dataqueue.full()
print(isfull)

返回隊列大小
size = dataqueue.qsize()
print(size)

 FIFO(先進的先出)
print(dataqueue.get())

# 注意:隊列是線程之間常用的數(shù)據(jù)交換形式,因為隊列在線程間,是線程安全的

線程池爬蟲

from concurrent.futures import ThreadPoolExecutor
import requests,threading
from lxml.html import etree
# 線程池的目的:創(chuàng)建一個線程池,里面有指定數(shù)量的線程,讓線程執(zhí)行任務(wù)

def down_load_data(page):
    print(page)
    print('正在下載第' + str(page) + '頁', threading.currentThread().name)
    full_url = 'http://blog.jobbole.com/all-posts/page/%s/' % str(page)
    req_header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2864.400'
    }
    response = requests.get(full_url, headers=req_header)

    if response.status_code == 200:
        # 將獲取的頁面源碼存到dataqueue隊列中
        print('請求成功')
        return response.text,response.status_code
def download_done(futures):
    print(futures.result())
    # 可以在這里做數(shù)據(jù)解析
    html = futures.result()[0]
    html_element = etree.HTML(html)
    articles = html_element.xpath('//div[@class="post floated-thumb"]')
    for article in articles:
        articleInfo = {}
        # 標題
        articleInfo['title'] = article.xpath('.//a[@class="archive-title"]/text()')[0]
        # 封面
        img_element = article.xpath('.//div[@class="post-thumb"]/a/img')
        if len(img_element) > 0:
            articleInfo['coverImage'] = img_element[0].xpath('./@src')[0]
        else:
            articleInfo['coverImage'] = '暫無圖片'
        p_as = article.xpath('.//div[@class="post-meta"]/p[1]//a')
        if len(p_as) > 2:
            # tag類型
            articleInfo['tag'] = p_as[1].xpath('./text()')[0]
            # 評論量
            articleInfo['commentNum'] = p_as[2].xpath('./text()')[0]
        else:
            # tag類型
            articleInfo['tag'] = p_as[1].xpath('./text()')[0]
            # 評論量
            articleInfo['commentNum'] = '0'
        # 簡介
        articleInfo['content'] = article.xpath('.//span[@class="excerpt"]/p/text()')[0]
        # 時間
        articleInfo['publishTime'] = ''.join(article.xpath('.//div[@class="post-meta"]/p[1]/text()')).replace('\n',
                                                                                                              '').replace(
            ' ', '').replace('\r', '').replace('·', '')
        print(articleInfo)
if __name__ == '__main__':

    # 創(chuàng)建線程池
    # max_workers:指定線程池中的線程數(shù)量
    pool = ThreadPoolExecutor(max_workers=10)

    for i in range(1, 201):
        # 往線程池中添加任務(wù)
        handler=pool.submit(down_load_data,i)
        # 設(shè)置回調(diào)方法
        handler.add_done_callback(download_done)
    # 內(nèi)部實質(zhì)是執(zhí)行了join()方法
    pool.shutdown()

進程

  • 進程:是操作系統(tǒng)進行資源分配的基本單元,進程的執(zhí)行也是無序的,每一個進程都有自己儲存空間,進程之間的資源不共享
from multiprocessing import Process,Queue
import os
# maxsize=-1 :設(shè)置隊列中能夠存儲最大元素的個數(shù)
data_queue = Queue(maxsize=10,)
def write_data(num,data_queue):

    print(num)
    for i in range(0,num):
        data_queue.put(i)
    print(os.getpid(),data_queue.full())


def read_data(data_queue):
    print('正在讀取',os.getpid())
    print(data_queue.qsize())
    for i in range(0,data_queue.qsize()):
        print(data_queue.get())



if __name__ == '__main__':
    # os.getpid() 獲取進程id
    print('主進程開始',os.getpid())
    # 創(chuàng)建子進程
    '''
    target=None,設(shè)置進程要執(zhí)行的函數(shù)
    name=None, 設(shè)置進程名稱
    args=(),給進程執(zhí)行的函數(shù)傳遞參數(shù)(tuple類型)
    kwargs={},給進程執(zhí)行的函數(shù)傳遞參數(shù)(字典類型)
    '''
    process1 = Process(target=write_data,args=(10,data_queue))
    # 使用start()啟動進程
    process1.start()
    # timeout=5:設(shè)置阻塞時間
    process1.join(timeout=5)

    process2 = Process(target=read_data,args=(data_queue,))
    # 使用start()啟動進程
    process2.start()
    process2.join()
    print('主進程結(jié)束', os.getpid())
'''
1.創(chuàng)建任務(wù)隊列
2.創(chuàng)建爬取進程,執(zhí)行爬取任務(wù)
3.創(chuàng)建數(shù)據(jù)隊列
4.創(chuàng)建解析線程,解析獲取數(shù)據(jù)

案例:世紀佳緣
武漢 url
http://date.jiayuan.com/eventslist_new.php?page=1&city_id=4201&shop_id=33 (第一頁是靜態(tài)頁面)
http://date.jiayuan.com/eventslist_new.php?page=2&city_id=4201&shop_id=33 (第二頁動態(tài)加載)
http://date.jiayuan.com/eventslist_new.php?page=3&city_id=4201&shop_id=33
accessID=20181222135209518625;
 SESSION_HASH=8f0e4c42c7aa889792b3d1a9229a6ac6ed4b3296;
 user_access=1; PHPSESSID=4efcd71c786c42502ee0b39fcdc0e601; plat=date_pc; DATE_FROM=daohang; DATE_SHOW_LOC=4201; DATE_SHOW_SHOP=33; uv_flag=124.200.191.230
上海 url
http://date.jiayuan.com/eventslist_new.php?page=2&city_id=31&shop_id=15
accessID=20181222135209518625;
 SESSION_HASH=8f0e4c42c7aa889792b3d1a9229a6ac6ed4b3296;
 user_access=1; PHPSESSID=4efcd71c786c42502ee0b39fcdc0e601; plat=date_pc; DATE_FROM=daohang; DATE_SHOW_LOC=31; uv_flag=124.205.158.242; DATE_SHOW_SHOP=15
'''
from multiprocessing import Process, Queue
import requests, re, json
from lxml.html import etree
import time


def down_load_page_data(taskqueue, dataqueue):
    '''
    2 執(zhí)行任務(wù)下載
    :param taskqueue:
    :param dataqueue:
    :return:
    '''
    sumTime = 0
    while True:
        if not taskqueue.empty():
            sumTime = 0
            url = taskqueue.get()
            response, cur_page = download_page_data(url)
            data_dict = {'data': response.text, 'page': cur_page}
            dataqueue.put(data_dict)

            # 獲取下一頁
            if cur_page != 1:
                print('===',cur_page)
                if isinstance(response.json(), list):

                    next_page = cur_page + 1
                    # re.compile('page=\d+')
                    next_url = re.sub('page=\d+', 'page=' + str(next_page), url)
                    taskqueue.put(next_url)
                else:
                    print('已獲取到' + str(cur_page) + '頁', '沒有數(shù)據(jù)了', response.json())
                    pass

            elif cur_page == 1:
                next_page = cur_page + 1
                next_url = re.sub('page=\d+', 'page=' + str(next_page), url)
                taskqueue.put(next_url)
        else:
            #數(shù)據(jù)隊列中沒有任務(wù)了
            time.sleep(0.001)
            sumTime = sumTime + 1
            if sumTime > 5000:
                print('跳出循環(huán)')
                break

def download_page_data(url):
    '''
     3 下載每一個分頁數(shù)據(jù)
    :param url: 每一個分頁url地址
    :return:
    '''
    # http://date.jiayuan.com/eventslist_new.php?page=2&city_id=4201&shop_id=33
    pattern = re.compile('.*?page=(\d+)&city_id=(\d+)&shop_id=(\d+)')
    result = re.findall(pattern, url)[0]
    # 當(dāng)前頁
    cur_page = result[0]
    DATE_SHOW_LOC = result[1]
    DATE_SHOW_SHOP = result[2]
    print(cur_page, DATE_SHOW_SHOP, DATE_SHOW_LOC)
    cookie = '''
           accessID=20181222135209518625; SESSION_HASH=8f0e4c42c7aa889792b3d1a9229a6ac6ed4b3296; user_access=1; PHPSESSID=4efcd71c786c42502ee0b39fcdc0e601; plat=date_pc; DATE_FROM=daohang; uv_flag=124.200.191.230; DATE_SHOW_LOC=%s; DATE_SHOW_SHOP=%s
    ''' %(DATE_SHOW_LOC,DATE_SHOW_SHOP)
    # print(cookie)

    req_header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2864.400',
        'Cookie': cookie,
        'Referer': 'http://date.jiayuan.com/eventslist.php',
    }
    # cookie_dict = {sub_str.split('=')[0]: sub_str.split('=')[1] for sub_str in cookie.split('; ')}
    response = requests.get(url, headers=req_header)
    # print(cookie_dict)
    if response.status_code == 200:
        print('第' + cur_page + '頁獲取成功', DATE_SHOW_LOC, DATE_SHOW_SHOP)
        return response, int(cur_page)


def parse_page_data(dataqueue):
    '''
    解析進程解析數(shù)據(jù)
    :param dataqueue:
    :return:
    '''
    while not dataqueue.empty():
        data = dataqueue.get()
        page = data['page']
        html = data['data']
        if page == 1:
            print('解析第一頁數(shù)據(jù),靜態(tài)頁面')
            html_element = etree.HTML(html)
            hot_active = html_element.xpath('//div[@class="hot_detail fn-clear"]')
            for hot_div in hot_active:
                # 活動詳情的url地址
                full_detail_url = 'http://date.jiayuan.com' + hot_div.xpath('.//h2[@class="hot_title"]/a/@href')[0]
                response = download_detail_data(full_detail_url)
                parse_detail_data(response)
            more_active = html_element.xpath('//ul[@class="review_detail fn-clear t-activiUl"]/li')
            for more_li in more_active:
                # 活動詳情的url地址
                full_detail_url = 'http://date.jiayuan.com' + more_li.xpath('.//a[@class="review_link"]/@href')[0]
                response = download_detail_data(full_detail_url)
                parse_detail_data(response)
        else:
            print('解析第' + str(page) + '數(shù)據(jù)', '非靜態(tài)頁面')
            # 使用json.loads() 將json字符串轉(zhuǎn)換為python數(shù)據(jù)類型
            json_obj = json.loads(html)
            if isinstance(json_obj,list):
                # 是列表說明得到正確的數(shù)據(jù)
                print('正在解析數(shù)據(jù)')
                for sub_dict in json_obj:
                    id = sub_dict['id']
                    full_detail_url = 'http://date.jiayuan.com/activityreviewdetail.php?id=%s' % id
                    response = download_detail_data(full_detail_url)
                    parse_detail_data(response)

def download_detail_data(url):
    req_header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2864.400',
        'Cookie': 'accessID=20181222135209518625; SESSION_HASH=8f0e4c42c7aa889792b3d1a9229a6ac6ed4b3296; user_access=1; PHPSESSID=4efcd71c786c42502ee0b39fcdc0e601; plat=date_pc; DATE_FROM=daohang; uv_flag=124.200.191.230; DATE_SHOW_LOC=31; DATE_SHOW_SHOP=15',
        'Referer': 'http://date.jiayuan.com/eventslist.php',
    }
    # cookie_dict = {sub_str.split('=')[0]: sub_str.split('=')[1] for sub_str in cookie.split('; ')}
    response = requests.get(url, headers=req_header)
    # print(cookie_dict)
    if response.status_code == 200:
        print('詳情頁面獲取成功', response.url)
        return response


def parse_detail_data(response):
    '''
    解析活動詳情
    :param response:
    :return:
    '''
    html_element = etree.HTML(response.text)

    # 創(chuàng)建一個字典棚辽,存放獲取的數(shù)據(jù)
    item = {}
    # 活動標題
    item['title'] = ''.join(html_element.xpath('//h1[@class="detail_title"]/text()')[0])
    # 活動時間
    item['time'] = ','.join(
        html_element.xpath('//div[@class="detail_right fn-left"]/ul[@class="detail_info"]/li[1]//text()')[0])
    # 活動地址
    item['adress'] = html_element.xpath('//ul[@class="detail_info"]/li[2]/text()')[0]
    # 參加人數(shù)
    item['joinnum'] = html_element.xpath('//ul[@class="detail_info"]/li[3]/span[1]/text()')[0]
    # 預(yù)約人數(shù)
    item['yuyue'] = html_element.xpath('//ul[@class="detail_info"]/li[3]/span[2]/text()')[0]
    # 介紹
    item['intreduces'] = \
        html_element.xpath('//div[@class="detail_act fn-clear"][1]//p[@class="info_word"]/span[1]/text()')[0]
    # 提示
    item['point'] = html_element.xpath('//div[@class="detail_act fn-clear"][2]//p[@class="info_word"]/text()')[0]
    # 體驗店介紹
    item['introductionStore'] = ''.join(
        html_element.xpath('//div[@class="detail_act fn-clear"][3]//p[@class="info_word"]/text()'))
    # 圖片連接
    item['coverImage'] = html_element.xpath('//div[@class="detail_left fn-left"]/img/@data-original')[0]

    print(item)

    with open('sjjy.json', 'a+', encoding='utf-8') as file:
        json_str = json.dumps(item,ensure_ascii=False)+'\n'
        file.write(json_str)

if __name__ == '__main__':
    # 第一步 創(chuàng)建任務(wù)隊列
    taskqueue = Queue()

    # 2 設(shè)置起始任務(wù)
    taskqueue.put('http://date.jiayuan.com/eventslist_new.php?page=1&city_id=4201&shop_id=33')
    taskqueue.put('http://date.jiayuan.com/eventslist_new.php?page=1&city_id=31&shop_id=15')
    taskqueue.put('http://date.jiayuan.com/eventslist_new.php?page=1&city_id=3702&shop_id=42')
    taskqueue.put('http://date.jiayuan.com/eventslist_new.php?page=1&city_id=50&shop_id=5')

    # 4 創(chuàng)建數(shù)據(jù)隊列
    dataqueue = Queue()
    # 3 創(chuàng)建進程爬取任務(wù)

    for i in range(0, 4):
        process_crawl = Process(
            target=down_load_page_data,
            args=(taskqueue, dataqueue)
        )
        process_crawl.start()
    time.sleep(10)

    # 創(chuàng)建解析進程
    for i in range(0, 4):
        process_parse = Process(
            target=parse_page_data,
            args=(dataqueue,)
        )
        process_parse.start()

進程池

# from concurrent.futures import ProcessPoolExecutor
# import time,os
# def download_data(page):
#     print(page,os.getpid())
#     # time.sleep(1)
#     return '請求成功'+str(page),page
#
# # 進程執(zhí)行完一個任務(wù)后的回調(diào)函數(shù)
# def download_done(futures):
#     result =futures.result()
#     print(result)
#
#     next_page = int(result[1])+1
#     handler=pool.submit(download_data,next_page)
#     handler.add_done_callback(download_done)
# if __name__ == '__main__':
#     # 1 創(chuàng)建進程池
#     # max_workers= : 設(shè)置進程池中的進程數(shù)量
#     pool = ProcessPoolExecutor(4)
#
#     for i in range(0,200):
#         '''
#         fn, 執(zhí)行函數(shù)
#         *args, 傳遞參數(shù)
#         '''
#         handler = pool.submit(download_data,i)
#         # 回調(diào)函數(shù)的設(shè)置,,看自己是否需要
#         handler.add_done_callback(download_done)
#     # cannot schedule new futures after shutdown
#     # pool.shutdown()

# 方式二
from multiprocessing import Pool
import os
def download_data(page):
    print(page,os.getpid())
    # time.sleep(1)
    return '請求成功'+str(page),page
def done(futures):
    print(futures)

if __name__ == '__main__':
    # 創(chuàng)建進程池
    pool = Pool(4)

    for page in range(0,200):
        # pool.apply_async() 異步非阻塞
        # pool.apply() 同步的方式添加任務(wù)
        # func : 要執(zhí)行的方法
        # args=() ,給函數(shù)傳遞的參數(shù)
        #callback=None, 成功回調(diào)
        #error_callback=None  錯誤回調(diào)
        pool.apply_async(download_data,args=(page,),callback=done)
    # 執(zhí)行close 后不可以在添加任務(wù)了
    pool.close()
    pool.join()

進程池爬蟲

from concurrent.futures import ProcessPoolExecutor
import requests, re, time, json
from lxml.html import etree


def down_load_page_data(url):
    """
    執(zhí)行任務(wù)的下載
    :param url
    :return:
    """
    response, cur_page = download_page_data(url)
    data_dict = {'data': response.text, 'page': cur_page}

    # 獲取下一頁
    if cur_page != 1:
        print('====', cur_page)
        if isinstance(response.json(), list):
            next_page = cur_page + 1
            next_url = re.sub('page=\d+', 'page=' + str(next_page), url)

        else:
            print('已獲取到' + str(cur_page) + '頁', '沒有數(shù)據(jù)了', response.json())
            next_url = None
            pass
    elif cur_page == 1:
        next_page = cur_page + 1
        next_url = re.sub('page=\d+', 'page=' + str(next_page), url)

    return data_dict, next_url


def download_page_data(url):
    """
    下載每一個分頁的數(shù)據(jù)
    :param url: 每一個分頁的url地址
    :return:
    """
    # http://date.jiayuan.com/eventslist_new.php?
    # page=1&city_id=4201&shop_id=33
    pattern = re.compile('.*?page=(\d+)&city_id=(\d+)&shop_id=(\d+)')
    result = re.findall(pattern, url)[0]
    cur_page = result[0]
    DATE_SHOW_LOC = result[1]
    DATE_SHOW_SHOP = result[2]
    print(cur_page, DATE_SHOW_SHOP, DATE_SHOW_LOC)
    cookie = """_gscu_1380850711=43812116hs5dyy11; accessID=20181222071935501079; jy_refer=www.baidu.com; _gscbrs_1380850711=1; PHPSESSID=9202a7e752f801a49a5747832520f1da; plat=date_pc; DATE_FROM=daohang; SESSION_HASH=61e963462c6b312ee1ffacf151ffaa028477217d; user_access=1; uv_flag=124.64.18.38; DATE_SHOW_LOC=%s; DATE_SHOW_SHOP=%s""" % (
    DATE_SHOW_LOC, DATE_SHOW_SHOP)
    # print(cookie)

    req_header = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
        'Cookie': cookie,
        'Referer': 'http://date.jiayuan.com/eventslist.php',
    }
    # cookie_dict = {sub_str.split('=')[0]:sub_str.split('=')[1] for sub_str in cookie.split('; ')}
    # print(cookie_dict)
    # cookies(cookiejar object or dict)
    response = requests.get(url, headers=req_header)

    if response.status_code == 200:
        print('第' + cur_page + '頁獲取成功', DATE_SHOW_SHOP, DATE_SHOW_LOC)
        return response, int(cur_page)


def parse_page_data(futures):
    '''
    1.獲取到下一頁url地址,繼續(xù)往進程池中添加任務(wù)
    2.獲取到分頁的頁面源碼進行數(shù)據(jù)解析
    :param futures:
    :return:
    '''
    result = futures.result()
    data = result[0]
    next_page_url = result[1]
    if next_page_url:
        print('正在添加任務(wù)', next_page_url)
        handler = page_pool.submit(down_load_page_data, next_page_url)
        handler.add_done_callback(parse_page_data)
    page = data['page']
    html = data['data']
    # 創(chuàng)建進程池(獲取活動詳情的頁面源碼)
    detail_pool = ProcessPoolExecutor(2)

    if page == 1:
        print('解析第一頁數(shù)據(jù),靜態(tài)頁面')
        html_element = etree.HTML(html)
        hot_active = html_element.xpath('//div[@class="hot_detail fn-clear"]')
        for hot_div in hot_active:
            # 活動詳情的url地址
            full_detail_url = 'http://date.jiayuan.com' + hot_div.xpath('.//h2[@class="hot_title"]/a/@href')[0]
            handler = detail_pool.submit(download_detail_data, full_detail_url)
            handler.add_done_callback(parse_detail_data)
        more_active = html_element.xpath('//ul[@class="review_detail fn-clear t-activiUl"]/li')
        for more_li in more_active:
            # 活動詳情的url地址
            full_detail_url = 'http://date.jiayuan.com' + more_li.xpath('.//a[@class="review_link"]/@href')[0]
            handler = detail_pool.submit(download_detail_data, full_detail_url)
            handler.add_done_callback(parse_detail_data)
    else:
        print('解析第' + str(page) + '數(shù)據(jù)', '非靜態(tài)頁面')
        # 使用json.loads()將json字符串轉(zhuǎn)換為python數(shù)據(jù)類型
        json_obj = json.loads(html)
        if isinstance(json_obj, list):
            # 是列表,說明得到的是正確的數(shù)據(jù),
            print('正在解析數(shù)據(jù)')
            for sub_dict in json_obj:
                id = sub_dict['id']
                # http://date.jiayuan.com/activityreviewdetail.php?id=11706
                full_detail_url = 'http://date.jiayuan.com/activityreviewdetail.php?id=%s' % id
                handler = detail_pool.submit(download_detail_data, full_detail_url)
                handler.add_done_callback(parse_detail_data)
    detail_pool.shutdown()


def download_detail_data(url):
    """
    根據(jù)活動詳情的url地址發(fā)起請求
    :param url:
    :return:
    """
    req_header = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
        'Cookie': '_gscu_1380850711=43812116hs5dyy11; accessID=20181222071935501079; jy_refer=www.baidu.com; _gscbrs_1380850711=1; PHPSESSID=9202a7e752f801a49a5747832520f1da; plat=date_pc; DATE_FROM=daohang; SESSION_HASH=61e963462c6b312ee1ffacf151ffaa028477217d; user_access=1; uv_flag=124.64.18.38; DATE_SHOW_LOC=50; DATE_SHOW_SHOP=5',
        'Referer': 'http://date.jiayuan.com/eventslist.php',
    }
    response = requests.get(url, headers=req_header)

    if response.status_code == 200:
        print('詳情頁面獲取成功', response.url)
        return response


def parse_detail_data(futures):
    """
    解析活動詳情
    :param response:
    :return:
    """
    response = futures.result()
    html_element = etree.HTML(response.text)
    # 創(chuàng)建一個字典,存放獲取的數(shù)據(jù)
    item = {}
    # 活動標題
    item['title'] = ''.join(html_element.xpath('//h1[@class="detail_title"]/text()')[0])
    # 活動時間
    item['time'] = ','.join(
        html_element.xpath('//div[@class="detail_right fn-left"]/ul[@class="detail_info"]/li[1]//text()')[0])
    # 活動地址
    item['adress'] = html_element.xpath('//ul[@class="detail_info"]/li[2]/text()')[0]
    # 參加人數(shù)
    item['joinnum'] = html_element.xpath('//ul[@class="detail_info"]/li[3]/span[1]/text()')[0]
    # 預(yù)約人數(shù)
    item['yuyue'] = html_element.xpath('//ul[@class="detail_info"]/li[3]/span[2]/text()')[0]
    # 介紹
    item['intreduces'] = \
    html_element.xpath('//div[@class="detail_act fn-clear"][1]//p[@class="info_word"]/span[1]/text()')[0]
    # 提示
    item['point'] = html_element.xpath('//div[@class="detail_act fn-clear"][2]//p[@class="info_word"]/text()')[0]
    # 體驗店介紹
    item['introductionStore'] = ''.join(
        html_element.xpath('//div[@class="detail_act fn-clear"][3]//p[@class="info_word"]/text()'))
    # 圖片連接
    item['coverImage'] = html_element.xpath('//div[@class="detail_left fn-left"]/img/@data-original')[0]

    with open('shijijiyua.json', 'a+', encoding='utf-8') as file:
        json_str = json.dumps(item, ensure_ascii=False) + '\n'
        file.write(json_str)


if __name__ == '__main__':

    # 1 創(chuàng)建一個進程池,執(zhí)行分頁任務(wù)下載
    page_pool = ProcessPoolExecutor(2)
    # 2 添加任務(wù)
    start_urls = [
        'http://date.jiayuan.com/eventslist_new.php?page=1&city_id=4201&shop_id=33',
        'http://date.jiayuan.com/eventslist_new.php?page=1&city_id=31&shop_id=15',
        'http://date.jiayuan.com/eventslist_new.php?page=1&city_id=3702&shop_id=42',
        'http://date.jiayuan.com/eventslist_new.php?page=1&city_id=50&shop_id=5',
    ]
    for url in start_urls:
        handler = page_pool.submit(down_load_page_data, url)
        handler.add_done_callback(parse_page_data)

迭代器

迭代是訪問集合元素的一種方式冰肴。迭代器是一個可以記住遍歷的位置的對象屈藐。迭代器對象從集合的第一個元素開始訪問榔组,直到所有的元素被訪問完結(jié)束。迭代器只能往前不會后退

可迭代對象 我們已經(jīng)知道可以對list联逻、tuple瓷患、str等類型的數(shù)據(jù)使用for...in...的循環(huán)語法從其中依次拿到數(shù)據(jù)進行使用,我們把這樣的過程稱為遍歷遣妥,也叫迭代擅编。
如何判斷一個對象是否可以迭代

from collections import Iterable

print(isinstance([],Iterable)) --> True

print(isinstance(1,Iterable)) -->False

可迭代對象進行迭代使用的過程,每迭代一次(即在for...in...中每循環(huán)一次)都會返回對象中的下一條數(shù)據(jù)箫踩,一直向后讀取數(shù)據(jù)直到迭代了所有數(shù)據(jù)后結(jié)束爱态。

可迭代對象通過_iter方法向我們提供一個迭代器,我們在迭代一個可迭代對象的時候境钟,實際上就是先獲取該對象提供的一個迭代器锦担,然后通過這個迭代器來依次獲取對象中的每一個數(shù)據(jù)。一個具備了 _iter 方法的對象慨削,就是一個 可迭代對象

iter()函數(shù)與next()函數(shù)

list洞渔、tuple等都是可迭代對象,我們可以通過iter()函數(shù)獲取這些可迭代對象的迭代器缚态。然后我們可以對獲取到的迭代器不斷使用next()函數(shù)來獲取下一條數(shù)據(jù)磁椒。iter()函數(shù)實際上就是調(diào)用了可迭代對象的iter方法。

如何判斷一個對象是否是迭代器

from collections import Iterator

print(isinstance([1,2], Iterator)) -->False

print(isinstance(iter([1,2]), Iterator)) -->True

迭代器是用來幫助我們記錄每次迭代訪問到的位置玫芦,當(dāng)我們對迭代器使用next()函數(shù)的時候浆熔,迭代器會向我們返回它所記錄位置的下一個位置的數(shù)據(jù)。實際上桥帆,在使用next()函數(shù)的時候医增,調(diào)用的就是迭代器對象的next方法。所以老虫,我們要想構(gòu)造一個迭代器叶骨,就要實現(xiàn)它的next方法。并且python要求迭代器本身也是可迭代的祈匙,所以我們還要為迭代器實現(xiàn)iter方法忽刽,迭代器的iter方法返回自身即可。

一個實現(xiàn)了iter方法和next方法的對象菊卷,就是迭代器缔恳。

生成器

利用迭代器,我們可以在每次迭代獲取數(shù)據(jù)(通過next()方法)時按照特定的規(guī)律進行生成洁闰。但是我們在實現(xiàn)一個迭代器時歉甚,關(guān)于當(dāng)前迭代到的狀態(tài)需要我們自己記錄,進而才能根據(jù)當(dāng)前狀態(tài)生成下一個數(shù)據(jù)扑眉。為了達到記錄當(dāng)前狀態(tài)纸泄,并配合next()函數(shù)進行迭代使用赖钞,我們可以采用更簡便的語法,即生成器(generator)聘裁。生成器是一類特殊的迭代器雪营。
在使用生成器實現(xiàn)的方式中,我們將原本在迭代器next方法中實現(xiàn)的基本邏輯放到一個函數(shù)中來實現(xiàn)衡便,但是將每次迭代返回數(shù)值的return換成了yield献起,此時新定義的函數(shù)便不再是函數(shù),而是一個生成器了镣陕。
使用了yield關(guān)鍵字的函數(shù)不再是函數(shù)谴餐,而是生成器。(使用了yield的函數(shù)就是生成器)

  • yield關(guān)鍵字有兩點作用:
    保存當(dāng)前運行狀態(tài)(斷點)呆抑,然后暫停執(zhí)行岂嗓,即將生成器(函數(shù))掛起
    將yield關(guān)鍵字后面表達式的值作為返回值返回,此時可以理解為起到了return的作用
    可以使用next()函數(shù)讓生成器從斷點處繼續(xù)執(zhí)行.

協(xié)程

協(xié)程鹊碍,又稱微線程厌殉,纖程
協(xié)程是python個中另外一種實現(xiàn)多任務(wù)的方式,只不過比線程更小占用更小執(zhí)行單元(理解為需要的資源)侈咕。 它自帶CPU寄存器上下文公罕。這樣只要在合適的時機, 我們可以把一個協(xié)程 切換到另一個協(xié)程乎完。 只要這個過程中保存或恢復(fù) CPU上下文那么程序還是可以運行的熏兄。

協(xié)程和線程差異

在實現(xiàn)多任務(wù)時, 線程切換從系統(tǒng)層面遠不止保存和恢復(fù) CPU上下文這么簡單。 操作系統(tǒng)為了程序運行的高效性每個線程都有自己緩存Cache等等數(shù)據(jù)树姨,操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作。 所以線程的切換非常耗性能桥状。但是協(xié)程的切換只是單純的操作CPU的上下文帽揪,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住。

yeild簡單實現(xiàn)

import time

def work1():
    while True:
        print("----work1---")
        yield
        time.sleep(0.5)

def work2():
    while True:
        print("----work2---")
        yield
        time.sleep(0.5)

def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == "__main__":
    main()
實質(zhì): 其實任務(wù)是在主線程中并發(fā)執(zhí)行的辅斟,看上去像同時執(zhí)行而已转晰,當(dāng)執(zhí)行next()的時候,函數(shù)執(zhí)行到y(tǒng)ield的時候先暫停一下士飒,然后之后再調(diào)用next()的時候查邢,接著上一次暫停的位置執(zhí)行

實現(xiàn)協(xié)程

.greenlet的使用

from greenlet import greenlet
import requests

def download1():
    print('正在下載1')
    #耗時的操作
    response = requests.get(url='https://github.com/')
    gre2.switch()
    print('download1下載完了')
    gre2.switch()


def download2():
    print('正在下載2')
    response = requests.get(url='https://github.com/')
    gre1.switch()
    print('download2下載完了')


gre1 = greenlet(download1)
gre2 = greenlet(download2)
gre1.switch()

greenlet已經(jīng)實現(xiàn)了協(xié)程,但是這個還的人工切換酵幕,python還有一個比greenlet更強大的并且能夠自動切換任務(wù)的模塊gevent

gevent能夠在內(nèi)部自己實現(xiàn)攜程之間的切換

from gevent import monkey,pool
import gevent,requests
import lxml.etree as etree

有耗時操作時需要

monkey.patch_all() # 將程序中用到的耗時操作的代碼扰藕,換為gevent中自己實現(xiàn)的模塊

def download(url):
    print(url+'正在下載1')
    header = {'User-Agent':'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0'}
    response = requests.get(url,headers=header)
    print(len(response.text),url+'已完成1')

def download2(url):
    print(url+'正在下載2')
    header = {'User-Agent':'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0'}
    response = requests.get(url,headers=header)
    print(len(response.text),url+'已完成2')

pool = pool.Pool(2)

gevent.joinall(
    [
        pool.spawn(download,'https://www.yahoo.com/'),
        pool.spawn(download,'https://www.taobao.com/'),
        pool.spawn(download,'https://github.com/'), 
        pool.spawn(download2,'https://www.yahoo.com/'),
        pool.spawn(download2,'https://www.taobao.com/'),
        pool.spawn(download2,'https://github.com/'), 
    ]
)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芳撒,隨后出現(xiàn)的幾起案子邓深,更是在濱河造成了極大的恐慌未桥,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芥备,死亡現(xiàn)場離奇詭異冬耿,居然都是意外死亡,警方通過查閱死者的電腦和手機萌壳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門亦镶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人袱瓮,你說我怎么就攤上這事缤骨。” “怎么了懂讯?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵荷憋,是天一觀的道長。 經(jīng)常有香客問我褐望,道長勒庄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任瘫里,我火速辦了婚禮实蔽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谨读。我一直安慰自己局装,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布劳殖。 她就那樣靜靜地躺著铐尚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哆姻。 梳的紋絲不亂的頭發(fā)上宣增,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音矛缨,去河邊找鬼爹脾。 笑死,一個胖子當(dāng)著我的面吹牛箕昭,可吹牛的內(nèi)容都是我干的灵妨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼落竹,長吁一口氣:“原來是場噩夢啊……” “哼泌霍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筋量,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烹吵,失蹤者是張志新(化名)和其女友劉穎碉熄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肋拔,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡锈津,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凉蜂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼梆。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窿吩,靈堂內(nèi)的尸體忽然破棺而出茎杂,到底是詐尸還是另有隱情,我是刑警寧澤纫雁,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布煌往,位于F島的核電站,受9級特大地震影響轧邪,放射性物質(zhì)發(fā)生泄漏刽脖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一忌愚、第九天 我趴在偏房一處隱蔽的房頂上張望曲管。 院中可真熱鬧,春花似錦硕糊、人聲如沸院水。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檬某。三九已至,卻和暖如春螟蝙,著一層夾襖步出監(jiān)牢的瞬間橙喘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工胶逢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饰潜。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓初坠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彭雾。 傳聞我的和親對象是個殘疾皇子碟刺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 線程 操作系統(tǒng)線程理論 線程概念的引入背景 進程 之前我們已經(jīng)了解了操作系統(tǒng)中進程的概念半沽,程序并不能單獨運行爽柒,只有...
    go以恒閱讀 1,641評論 0 6
  • 一. 操作系統(tǒng)概念 操作系統(tǒng)位于底層硬件與應(yīng)用軟件之間的一層.工作方式: 向下管理硬件,向上提供接口.操作系統(tǒng)進行...
    月亮是我踢彎得閱讀 5,967評論 3 28
  • 顧名思義,進程即正在執(zhí)行的一個過程者填。進程是對正在運行程序的一個抽象浩村。進程的概念起源于操作系統(tǒng),是操作系統(tǒng)最核心的概...
    SlashBoyMr_wang閱讀 1,137評論 0 2
  • 必備的理論基礎(chǔ) 1.操作系統(tǒng)作用: 隱藏丑陋復(fù)雜的硬件接口占哟,提供良好的抽象接口心墅。 管理調(diào)度進程,并將多個進程對硬件...
    drfung閱讀 3,541評論 0 5
  • 大家好榨乎,我是金銀花怎燥,一個沉默的70后,從十幾歲開始世界好像就把我遺忘了蜜暑,我也不爭氣地往自卑抑郁封閉的泥沼中越陷越深...
    金銀花_6fed閱讀 732評論 1 3