Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中

一.安装环境

1.下载pymongo关键包

.
Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中
在PyCharm菜单栏中,选择File,再选择Settings(如上图)
Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中
选择Project Interpreter,再点击加号(如上图)
Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中
(如上图)静待安装,出现如下图信息即安装成功
Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中
此时你就会在外面看到你安装的包,如下图
Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中
此时引入pymongo包就不会报错了
Python爬虫:多进程爬取网上图片并下载到本地,并将相关信息保存到mongodb数据库中

2.下载安装mongodb

这里就不详细介绍了,网上教程甚多

二.如题

1.首先引入包

import requests, re, json, pymongo
from multiprocessing import Pool
from urllib.parse import urlencode
from hashlib import md5

(1).requests,re,json包是python爬虫的基本包的,pymongo就是刚刚装的mongodb包
(2).第二行是实现多进程的进程池包
(3).第三行是实现将字典直接拼接进url中
(4).第四行实现简单加密

2.封装爬虫类

class JiePaiSpider(object):

(1).连接mongodb数据库

client = pymongo.MongoClient('localhost')
    db = client['jiepai']

db = client[‘xxx’]xxx为你创建的mongodb数据库的名称

(2).构造函数

def __init__(self):
     self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}

1.在此函数中放入我们的请求头,用以模仿浏览器访问网站
2.需要注意的是,不能将mongodb数据库的连接请求放在此函数中,否则会报TypeError: can’t pickle _thread.lock objectsde 错,因为进程池不能序列化含有进程锁的对象,而pymango数据库中含有线程锁,所以进程池无法序列化pymango对象

(3)请求列表页的json接口,获取列表页的图片信息

    def get_list_json(self, offset):
        """
        请求列表页的json接口,获取列表页中的图片信息。
        :param offset: 请求接口时的偏移量参数。(0,20,40......)
        :return:
        """
        # https://www.toutiao.com/search_content/?offset=0&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1&from=search_tab&pd=synthesis
        # 准备接口参数
        params = {
            'offset': offset,
            'format': 'json',
            'keyword': '街拍',
            'autoload': 'true',
            'count': '20',
            'cur_tab': '1',
            'from': 'search_tab',
            'pd': 'synthesis'
        }
        # 利用urlencode将url和准备的接口参数进行拼接
        api_url = 'https://www.toutiao.com/search_content/?' + urlencode(params)
        try:
        	# 获取请求
            response = requests.get(api_url, headers=self.headers)
            if response.status_code == 200:
                # 响应状态码是200, 说明GET请求成功,返回列表页json数据
                return response.text
            else:
                print('请求异常:url={}, status_code={}'.format(api_url, response.status_code))
                return None
        except Exception as e:
            print('请求异常:url={}, error={}'.format(api_url, e))
            return None

响应状态码:GET请求成功的响应码是200;POST:成功状态码是201;
#401、400、403、404等以4开头的状态码表示程序代码有问题;
#500、501、502、505等以5开头的状态码表示你所访问的网站服务器有问题,跟代码关系不大;

(4)解析列表页json数据,找到有图片的列表页

    def parse_list_json(self, json_str):
        """
        :param json_str:列表页json数据
        :return:
        """
        json_dict = json.loads(json_str)
        if 'data' in json_dict.keys():
            # 判断字典json_dict的所有键中是否包含‘data‘,如果有,可以解析,如果没有,可能没有数据或者发生异常了。
            data_list = json_dict.get('data', None)
            if data_list and len(data_list) > 0:
                # 说明还有数据,可以解析
                urls = []
                for item in data_list:
                    if 'single_mode' not in item and 'cell_type' not in item:
                        article_url = item['article_url']
                        urls.append(article_url)
                # 返回含有图片的列表页信息
                return urls

(5).获取每一张图片的链接数据

    def get_detail_page(self, detail_url):
        try:
        	# 获取请求
            response = requests.get(detail_url, headers=self.headers)
            if response.status_code ==200:
                # 响应状态码是200表示有数据,说明GET请求成功
                # 返回获取到的图片信息
                return response.text
            else:
                print('请求异常: url={}, status_code={}'.format(detail_url, response.status_code))
                return None
        except Exception as e:
            print('请求异常:url={}, error={}'.format(detail_url, e))
            return None

响应状态码同上

(6).解析并请求图片下载链接

 def parse_datail_page(self, detail_html):
        # \(: 表示对正则表达式中的(进行转义,转化为一个普通的字符。
        js_json_str = re.findall(re.compile(r'gallery: JSON.parse\((.*?)\),', re.S), detail_html)[0].replace('\\', '').strip('"')
        # 数据存在mongo中
        data_dict = json.loads(js_json_str)
        self.save_dict_to_db(json.loads(js_json_str))

        # 解析Json,取出图片的url地址,下载图片到本地
        for item_dict in data_dict['sub_images']:
            img_url = item_dict['url']
            # 根据图片url地址,下载图片
            self.download_image(img_url)

(7).下载图片并保存在本地文件中

    def download_image(self, img_url):
        response = requests.get(img_url, headers= self.headers)
        if response.status_code == 200:
            # response。text:获取的是文本资源:(json字符串、网页源代码)
            # 但是图片属于二进制资源,图片数据的传输是以二进制流的形式传输的,不在是字符串了
            content = response.content
            # md5()函数的参数需要一个bytes字节码,不能是str类型的字符串
            # hexdigest():获取md5加密后的结果。
            img_name = md5(img_url.encode('utf-8')).hexdigest()

            # 'w'写入普通文本;'wb':专门用于写入二进制数据(图片、音频、视频)
            f = open('imgs/{}.jpg'.format(img_name),'wb')
            f.write(content)
            f.close()
        else:
            print(' 图片请求失败:{}'.format(img_url)

1. response。text:获取的是文本资源:(json字符串、网页源代码)但是图片属于二进制资源,图片数据的传输是以二进制流的形式传输的,不在是字符串了
2. md5()函数的参数需要一个bytes字节码,不能是str类型的字符串
3. 'w’写入普通文本;‘wb’:专门用于写入二进制数据(图片、音频、视频)
**


(8).将图片相关信息保存在数据库中


    def save_dict_to_db(self, dic):
        self.db['img'].insert_one(dic)

(9).启动爬虫的端口函数

    def start_spider(self, offset):
        print('正在请求偏移量为{}的图片'.format(offset))
        json_str = self.get_list_json(offset)
        if json_str:
            urls =self.parse_list_json(json_str)
            for detail_url in urls:
                detail_html = self.get_detail_page(detail_url)
                if detail_html:
                    self.parse_datail_page(detail_html)

此函数调用上面我们所定义的一堆方法实现功能

(10).main方法

if __name__ == '__main__':
    jp = JiePaiSpider()
    # 用多进程执行爬虫
    pool = Pool(3)
    pool.map(jp.start_spider, [x for x in range(0,101) if x % 20==0])
    pool.close()
    pool.join()

注意:
1.x for x in range(0,101) if x % 20==0此段代码的含义是生成0,20,40,60,80五个数

完整源代码

# 使用多进程对街拍图片进行下载,并将图片相关信息保存到mongodb数据库中。
import requests, re, json, pymongo
from multiprocessing import Pool
from urllib.parse import urlencode
from hashlib import md5
class JiePaiSpider(object):
    # 进程池无法序列化pymango对象,因为pymango数据库中含有线程锁
    # TypeError: can't pickle _thread.lock objects
    # 建立pymongo的链接
    client = pymongo.MongoClient('localhost')
    db = client['jiepai']
    def __init__(self):
        self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}

    def get_list_json(self, offset):
        """
        请求列表页的json接口,获取列表页中的图片信息。
        :param offset: 请求接口时的偏移量参数。(0,20,40......)
        :return:
        """
        # https://www.toutiao.com/search_content/?offset=0&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1&from=search_tab&pd=synthesis
        # 准备接口参数
        params = {
            'offset': offset,
            'format': 'json',
            'keyword': '街拍',
            'autoload': 'true',
            'count': '20',
            'cur_tab': '1',
            'from': 'search_tab',
            'pd': 'synthesis'
        }
        api_url = 'https://www.toutiao.com/search_content/?' + urlencode(params)
        try:
            response = requests.get(api_url, headers=self.headers)
            if response.status_code == 200:
                # 响应状态码是200, 说明GET请求成功
                return response.text
            else:
                print('请求异常:url={}, status_code={}'.format(api_url, response.status_code))
                return None
        except Exception as e:
            print('请求异常:url={}, error={}'.format(api_url, e))
            return None

    def parse_list_json(self, json_str):
        """
        解析列表页json数据
        :param json_str:
        :return:
        """
        json_dict = json.loads(json_str)
        if 'data' in json_dict.keys():
            # 判断字典json_dict的所有键中是否包含‘data‘,如果有,可以解析,如果没有,可能没有数据或者发生异常了。
            data_list = json_dict.get('data', None)
            if data_list and len(data_list) > 0:
                # 说明还有数据,可以解析
                urls = []
                for item in data_list:
                    if 'single_mode' not in item and 'cell_type' not in item:
                        article_url = item['article_url']
                        urls.append(article_url)
                return urls
    def get_detail_page(self, detail_url):
        try:
            response = requests.get(detail_url, headers=self.headers)
            if response.status_code ==200:
                # 响应状态码是200表示有数据,说明GET请求成功
                return response.text
            else:
                print('请求异常: url={}, status_code={}'.format(detail_url, response.status_code))
                return None
        except Exception as e:
            print('请求异常:url={}, error={}'.format(detail_url, e))
            return None

    def parse_datail_page(self, detail_html):
        # \(: 表示对正则表达式中的(进行转义,转化为一个普通的字符。
        js_json_str = re.findall(re.compile(r'gallery: JSON.parse\((.*?)\),', re.S), detail_html)[0].replace('\\', '').strip(
            '"')
        # 数据存在mongo中
        data_dict = json.loads(js_json_str)
        self.save_dict_to_db(json.loads(js_json_str))
        # 解析Json,取出图片的url地址,下载图片到本地
        for item_dict in data_dict['sub_images']:
            img_url = item_dict['url']
            # 根据图片url地址,下载图片
            self.download_image(img_url)

    def download_image(self, img_url):
        response = requests.get(img_url, headers= self.headers)
        if response.status_code == 200:
            # response。text:获取的是文本资源:(json字符串、网页源代码)
            # 但是图片属于二进制资源,图片数据的传输是以二进制流的形式传输的,不在是字符串了
            content = response.content
            # md5()函数的参数需要一个bytes字节码,不能是str类型的字符串
            # hexdigest():获取md5加密后的结果。
            img_name = md5(img_url.encode('utf-8')).hexdigest()

            # 'w'写入普通文本;'wb':专门用于写入二进制数据(图片、音频、视频)
            f = open('imgs/{}.jpg'.format(img_name),'wb')
            f.write(content)
            f.close()
        else:
            print(' 图片请求失败:{}'.format(img_url))

    def save_dict_to_db(self, dic):
        self.db['img'].insert_one(dic)

    def start_spider(self, offset):
        print('正在请求偏移量为{}的图片'.format(offset))
        json_str = self.get_list_json(offset)
        if json_str:
            urls =self.parse_list_json(json_str)
            for detail_url in urls:
                detail_html = self.get_detail_page(detail_url)
                if detail_html:
                    self.parse_datail_page(detail_html)

if __name__ == '__main__':
    jp = JiePaiSpider()
    pool = Pool(3)
    pool.map(jp.start_spider, [x for x in range(0,101) if x % 20==0])
    pool.close()
    pool.join()