zoukankan      html  css  js  c++  java
  • Python3 爬虫爬取中国图书网(淘书团) 进阶版

    在原版的基础上,添加了进程池,进程锁,以及数据处理分析小实验

    原版的链接为:http://www.cnblogs.com/ChrisInsistPy/p/8981820.html

    首先分析一下在整个程序的哪个进程中,可以实现多进程提高运行效率,首先爬虫程序会先去拿网站的url,然后对url内的json数据进行处理,之后写入文件

    所以在整个过程中,我们可以分别让多个进程去拿url拿response的数据,然后分批处理,写入文件中

    这里有个知识点,就是在python中,每个进程都有一个GIL锁,确保同一时间只能有一个线程运行,所以多进程可能会更实用,具体可以google一下

    首先找到我们遍历url的方法中,把方法体进一步封装到  scraping_page_data()方法里

    创建线程池

    def dynamtic_scraping_data(page, headers, fileName):
        pool = multiprocessing.Pool() #进程池
        lock = multiprocessing.Manager().Lock() #加一个写锁,确保每页的内容在一起
        '''
        if code ' lock = multiprocessing.Lock()'报错:
        RuntimeError: Lock objects should only be shared between processes through inheritance
      '''  
        for i in range(page):
            pool.apply_async(scraping_page_data,(lock, i, headers, fileName))
        pool.close()
        pool.join()

    这里有几个知识点,首先,对于进程池,会根据主机的系统资源,去分配到底有多少条进程

    其次如果不调用join方法,子进程会在主进程结束后继续执行,因为后面有数据分析的功能所以我们条用了join方法,让主进程去等待其他的进程

    在进程池中调用apply方法去实现每个子进程要执行的方法apply(method_name, (parameters....))

    由于我们想要确保,每次拿到一条url,希望当前进程写入的数据的时候,不被其他进程打扰,我们给写入操作加上进程锁

    在写的过程中,我发现如果按照创建一条进程的方法也创建进程锁,会报错,原因是用了进程池,而进程池中的进程并不是由当前同一个父进程创建的原因。

    查了下资料,multiprocessing.Manager()返回的manager对象控制了一个server进程,可用于多进程之间的安全通信。

    写入文件加锁

    def write_csv_rows(lock, path, headers, rows):
    # write one row
        lock.acquire()
        with open(path, 'a', encoding='gb18030', newline='') as f:
            f_csv = csv.DictWriter(f, headers)
             # 如果写入数据为字典,则写入一行,否则写入多行
            if type(rows) == type({}):
                f_csv.writerow(rows)
            else:
                f_csv.writerows(rows)
        lock.release()

    这样我们会发现进程池会调用多个进程资源,并执行写入操作,互不干扰,但是这样就遗留下来了问题,总结会提到

    接下来拿到数据后,我们读取cvs中的price,分析价格低于200¥的图书的价位分布,为了生成统计图,我们引入matplotlib包

    首先处理脏数据:

      这里提到一点在基础版忽视的问题,我们发现在读入的数据中,有一部分的图书名字会加上‘团购:’的无用信息,所以我们要在存入磁盘前删掉它

     for data in result['Data']:
                bookinfo['bookName'] = data['book_name'].replace('团购:','')
                bookinfo['price'] = data['group_price']
                bookinfo['iconLink'] = data['group_image']
                write_csv_rows(lock, fileName,headers,bookinfo)

    接下来就是处理数据的部分了:先拿到cvs文件中price的值,也就是栏位为1

    然后遍历删掉title => price后,我们发现在price 的数据中,有的值为'39.9-159.9'格式,对于这种格式的数据,我们求平均值来计算

    最后我们把价格低于200的数据存入数组,用于生成柱状图

     #matplotlib views, 统计价格小于200的图书价格分布
        prices = []
        price = read_csv_column(csv_filename, 1)
        for i in range(len(price)-1):
            if(price[i+1].find('-') != -1):
                splitprice = price[i+1].split('-')
                average_price = (float(splitprice[0]) + float(splitprice[1]))/ 2
                if(average_price < 200):
                    prices.append(average_price)
            elif(float(price[i+1]) < 200):
                prices.append(float(price[i+1]))
        plt.hist(prices, bins=10)
        print(prices)
        plt.show()

    调用show()方法后生成统计图

    当然matplotlib为我们提供了很多方法去美化,以及生成点状图,线图等等,在这里仅仅是测试功能,自我学习,不多测试了

    小实验全部代码:

    import requests
    from bs4 import BeautifulSoup
    import json
    import csv
    import multiprocessing
    from matplotlib import pyplot as plt
    def parse_one_page():
        header = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
            'Host': 'tuan.bookschina.com',
            'Referer': 'http://tuan.bookschina.com/',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'Accept-Encoding': 'gzip, deflate',
            'Accept-Language': 'zh-CN,zh;q=0.9'
        }
        url = 'http://tuan.bookschina.com/'
        response = requests.get(url, headers = header) #模仿浏览器登录
        response.encoding = 'utf-8'
        soup = BeautifulSoup(response.text,'html.parser')
        for item in soup.select('div .taoListInner ul li'):
            print(item.select('h2')[0].text) #返回对象为数组
            print(item.select('.salePrice')[0].text)
            print(item.select('img')[0].get('src')) #get方法用来取得tab内部的属性值
    
    
    def dynamtic_scraping_data(page, headers, fileName):
        pool = multiprocessing.Pool() #进程池
        lock = multiprocessing.Manager().Lock() #加一个写锁,确保每页的内容在一起
        '''
        if code ' lock = multiprocessing.Lock()'报错:
        RuntimeError: Lock objects should only be shared between processes through inheritance
        用了进程池,而进程池中的进程并不是由当前同一个父进程创建的原因。查了下资料,
        multiprocessing.Manager()返回的manager对象控制了一个server进程,可用于多进程之间
        的安全通信。
        '''
        for i in range(page):
            pool.apply_async(scraping_page_data,(lock, i, headers, fileName))
        pool.close()
        pool.join()
    
    
    def scraping_page_data(lock, page, headers, fileName):
            url = 'http://tuan.bookschina.com/Home/GroupList?Type=0&Category=0&Price=0&Order=11&Page=' + str(
                page+1) + '&Tyjson=true'
            response = requests.get(url)
            result = json.loads(response.text)
            bookinfo = {}
            for data in result['Data']:
                bookinfo['bookName'] = data['book_name'].replace('团购:','')
                bookinfo['price'] = data['group_price']
                bookinfo['iconLink'] = data['group_image']
                write_csv_rows(lock, fileName,headers,bookinfo)
            print(multiprocessing.current_process().name + ' ' + url)
    
    
    def write_csv_headers(path, headers):
        '''
        写入表头
        '''
        with open(path, 'a', encoding='gb18030', newline='') as f:
            f_csv = csv.DictWriter(f, headers)
            f_csv.writeheader()
    
    
    def write_csv_rows(lock, path, headers, rows):
    # write one row
        lock.acquire()
        with open(path, 'a', encoding='gb18030', newline='') as f:
            f_csv = csv.DictWriter(f, headers)
             # 如果写入数据为字典,则写入一行,否则写入多行
            if type(rows) == type({}):
                f_csv.writerow(rows)
            else:
                f_csv.writerows(rows)
        lock.release()
    
    def read_csv_column(path, column):
    # read one row
        with open(path, 'r', encoding='gb18030', newline='') as f:
            reader = csv.reader(f)
            return [row[column] for row in reader]
    
    def main(page):
        # parse_one_page() #Tip: beautifulSoup test
        csv_filename = "bookInfo.csv"
        headers = ['bookName', 'price', 'iconLink']
        write_csv_headers(csv_filename,headers)
        dynamtic_scraping_data(page, headers, csv_filename)
    
        #matplotlib views, 统计价格小于200的图书价格分布
        prices = []
        price = read_csv_column(csv_filename, 1)
        for i in range(len(price)-1):
            if(price[i+1].find('-') != -1):
                splitprice = price[i+1].split('-')
                average_price = (float(splitprice[0]) + float(splitprice[1]))/ 2
                if(average_price < 200):
                    prices.append(average_price)
            elif(float(price[i+1]) < 200):
                prices.append(float(price[i+1]))
        plt.hist(prices, bins=18)
        print(prices)
        plt.show()
    if __name__ == '__main__':
        main(20)

    总结:

    在测试爬虫程序的过程中,发现了一个非常关键的问题,也就是网络稳定对爬虫程序的影响。如果读取页数相当庞大,在抓取过程中,

    会有某个链接出现相应失败的情况,但是本程序默认的操作是跳过继续执行。所以这样就有可能导致数据丢失,以至于分析数据结果

    不准确,google后,发现消息队列是一个很好的解决方法,把每次拿到的url存入队列中,每个进程作为消费者去pop队列中的url,

    拿数据失败,则从新把url指令存入队列,等待重新执行。有机会试着实现一下

  • 相关阅读:
    加入创业公司有什么利弊
    Find Minimum in Rotated Sorted Array II
    Search in Rotated Sorted Array II
    Search in Rotated Sorted Array
    Find Minimum in Rotated Sorted Array
    Remove Duplicates from Sorted Array
    Spiral Matrix
    Spiral Matrix II
    Symmetric Tree
    Rotate Image
  • 原文地址:https://www.cnblogs.com/ChrisInsistPy/p/8986930.html
Copyright © 2011-2022 走看看