• 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指令存入队列,等待重新执行。有机会试着实现一下

  • 相关阅读:
    Python3学习笔记(十七):requests模块
    fiddler(四)、断点(转)
    fiddler(三)、会话框添加显示请求方法栏
    PostgreSQL12同步流复制搭建-同步不生效的问题、主库恢复后,无法与新主库同步问题
    PostgreSQL的count(*) count(1) count(列名)的区别
    CentOS系统日志(转)
    常用PostgreSQL HA(高可用)工具收集
    转-性能优化中CPU、内存、磁盘IO、网络性能的依赖
    PostgreSQL查询数据库中包含某种类型的表有哪些
    PostgreSQL中with和without time zone两者有什么区别
  • 原文地址:https://www.cnblogs.com/ChrisInsistPy/p/8986930.html
Copyright © 2020-2023  润新知