一、获取地址:
-
目标网址:http://www.maotiao.com,爬取目标:获得每个板块下所有图片的url地址并写入json文件(本次爬虫不在pipe管道中下载图片),获得所有网址后利用多线程来下载这些图片到本地;
-
分析网站:打开网站后,可以发现在主界面 >> 首页 一行后有八个版块 >‘性感…’,’西西…‘,等等;随便点击进入一个版块 后就可浏览到图集列表(每页有20个图引),页面底下有翻页,打开每个图集,可以浏览单个大图,底下也有 翻页;
-
根据上述页面情况可以了解到,这是一个具有三层结构的网站,使用scrapy可以很轻松实现,具体思路:
(1)获取网页第一层也就是首页的八个板块的地址(这些地址不是拿出来放在列表中,而是用yield迭代请求得到response交给自定义方法解析,以下同理);
(2)迭代到一个版块后,打开这个版块,首先浏览到的是图引列表(每页20个)的第一页,这一步可以获取到每个图引的地址,然后循环(这里的循环是要利用网页中的<下一页>标签里的地址来构造的)获取其它所有页的,这是进入到网站的第二层了,同样的yield迭代请求得到response交给自定义方法解析;
(3)迭代到一个图引(肯定上来也是第一页,能浏览到大图),从这个页面就获取到了目标网址了(大图的地址),利用下一页标签得到所有图片地址; -
代码实现
创建工程
cmd >> scrapy startproject maotiao
接着 >> cd maotiao
然后>> scrapy genspider maot miaotiao.com
创建完毕!
maot.py文件
# -*- coding: utf-8 -*-
import scrapy
from maotiao.items import MaotiaoItem
from urllib import parse #用来拼接网址
class MaotSpider(scrapy.Spider):
name = 'maot'
allowed_domains = ['www.maotiao.com']
start_urls = ['http://www.maotiao.com/']
n = 1 #后面每获取一个图片地址后加1,作为图片名
def parse(self,response):
#主解析程序,这里获取 start_urls = ['http://www.maotiao.com/'] 这个网页(也就是第一层网页)的response,
#这儿的目标是要得到八个版块的地址
for each in response.xpath('//*[@class="nav"]//a/@href').extract()[1:]: #因为得到列表包含<首页>这个标签,不需要所以去掉
yield scrapy.Request(each, callback=self.parse_get_srcs) # 将遍历到的一个版块的url用Request去请求页面得到response传给下面的parse_get_srcs去解析
def parse_get_srcs(self, response):
#这里就是解析网站第二层,也就是某个版块下能看到很多图引的那一页,这里要得到每个图引集刚打开的地址
#然后请求得到response传给下面的parse_imgsrc,它得到response后负责解析得到大图的地址
for each in response.xpath('//dl[@class="list-left public-box"]//dd')[0:-1]:
yield scrapy.Request(each.xpath('./a/@href').extract_first(), callback=self.parse_imgsrc)
###########################################################################################
# 因为需要翻页,所以要构造下一页的网址传给它自己不停的得到上面想要的结果
next_page = response.xpath('//a[contains(text(),"下一页")]/@href').extract_first()
if next_page is not None:
next_url = parse.urljoin(response.url,next_page)
yield scrapy.Request(next_url, callback=self.parse_get_srcs)
def parse_imgsrc(self, response):
# 解析第三层得到每个大图的地址并写入item
item = MaotiaoItem()
src = response.xpath('//div[@class="content-pic"]//img/@src').extract_first()
item['image_name'] = self.n #图片名称
item['image_url'] = src #图片地址
yield item #会被传到pipelines.py
print(item)
self.n += 1
###########################################################################################
# 打开某个图集后看大图同样需要翻页,相同的方法构造下一页的网址传给它自己不停的得到上面想要的结果
next_page = response.xpath('//a[contains(text(),"下一页")]/@href').extract_first()
if next_page is not None:
next_url = parse.urljoin(response.url,next_page)
yield scrapy.Request(next_url, callback=self.parse_imgsrc)
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import json
# 一种将传过来的item写入json文件的方法,该文件默认会生成在这个项目的主目录下
class MaotiaoPipeline(object):
def __init__(self):
self.json_file = open('url.json','a')
def process_item(self,item,spider):
line = json.dumps(dict(item)) + "\n"
self.json_file.write(line)
def spider_closed(self, spider):
self.json_file.close()
settings.py
暂只将以下代码块注释去掉即可!
ITEM_PIPELINES = {
'maotiao.pipelines.MaotiaoPipeline': 300,
}
运行 cmd>>cd maotiao
然后 >>scrapy crawl maot --nolog
爬虫运行并且可以看到打印结果,最后生成含有所有图片网址的url.json文件,大概有七千多张
import scrapy
#只要图片名和图片地址
class MaotiaoItem(scrapy.Item):
image_name = scrapy.Field()
image_url = scrapy.Field()
二、使用多线程或者多进程下载图片
- 如果不采用多线程或多进程来下载图片,将7000个图从网络取回本地是很漫长的,这里先定义一个普通的方法:
创建一个文件夹,在里面建一个子文件夹MN(用来放图片)和一个py文件,并将上面得到的url.json放在这里备用
import json
import requests
def get_image(img_name,url,timeout):
try:
res=requests.get(url,timeout=timeout)
with open('MN/%s.jpg' % img_name, mode='wb') as f:
f.write(res.content)
except:
return None
with open('url.json') as f:
js = json.load(f)
for item in js:
if item['image_url'] is not None: #会有部分网址得到的是None
url = item['image_url']
img_name = item['image_name']
get_image(img_name,url,20)
- 采用多进程multiprocessing
注释掉上述代码(留下get_image方法),添加以下:
比较简单的就是直接使用进程池Pool,用它的map方法
from multiprocessing import Pool
def get_photo(item):
if item['image_url'] is not None:
get_image(item['image_name'],item['image_url'],20)
print(item['image_name'])
def iter_item(js):
for item in js:
if item['image_url'] is not None:
yield item
if __name__=='__main__':
with open('url.json', 'r') as f:
js = json.load(f)
#得到一个包含所有图片网址的列表js后,创建4个进程
#为每个进程调用map(在这里可以理解为将上面定义的get_photo这个下载图片的方法分给这个进程,iterable就是这个方法要用到的参数,可以是一个列表或者一个生成器)
p = Pool(4) #创建4个进程,或者不给定默认传入cpu核数
#p.map(func=get_photo,iterable=[item for item in js]) #iterable可以传入列表
p.map(func=get_photo,iterable=iter_item(js)) #iterable也可以传入一个yield迭代器来不断输送网址过来,这里采用后者
p.close()
p.join()
运行后可以明显体会到速度的提升
3. 采用多线程
重新创建一个py文件,使用Queue队列来实现多线程方法(这里使用的是python3)
# coding=utf-8
from threading import Thread
import json,requests
from queue import Queue
class ThreadDload(Thread): #这个自定义类要继承Thread
def __init__(self,urlqueue): #这样子定义是为了实例化这个类时,可以把队列传进来供下面的自定义方法使用
super(ThreadDload,self).__init__() #固定语法,现在再回到main下面
self.urlqueue = urlqueue
def run(self): #当main下面的start()执行后,就会激活这个方法,在这里要取出队列里的值,传给get_image实现图片下载
while True:
try:
item = self.urlqueue.get(False) #取出队列里的item(是一个字典)
url = item['image_url']
img_name = item['image_name']
self.get_image(url,img_name,20) #调用下面下载图片的方法
print(url,'has done!')
except:
pass
def get_image(self,url,img_name,timeout):
try:
res = requests.get(url, timeout=timeout)
with open('MN\%s.jpg' % img_name, mode='wb') as f:
f.write(res.content)
except:
return None
if __name__ == '__main__':
#为了更好的理解,从这里开始,上面的自定义类及重写的方法先忽略
urlQueue = Queue() #首先实例一个队列类
with open('url.json') as f:
js = json.load(f)
for item in js:
if item['image_url'] is not None:
urlQueue.put(item) #使用队列的put方法将url.json的每一条记录都放在队列里(这会这个队列就像一个列表一样了,但不是,因为取值要用对应的get方法)
for i in range(20): #采用这种方式创建20个线程,事实上还可以更多
dlthread = ThreadDload(urlQueue) #这里创建的线程就是上面自定义线程类的实例化,它需要带上刚刚那个像列表一样的队列参数,这时再去上面看看如何自定义这个类的
#dlthread.daemon = True
dlthread.start() #看完上面怎么定义这个继承类后,这时要启动这个线程了,当执行这条语句后,就相当于启动了上面的run方法,这时再去看看run方法如何写的
print(dlthread.name,'start work.....')