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