茶已备好,只待君来!感谢关注 前端点线面 (>‿<),本号定期推荐原创深度好文,帮助每一位在前端领域打拼的伙伴们走向前列,此外关注我获取最前沿知识点、海量学习资料、《前端百题斩》、大量思维导图,并进前端划水交流群。

前言

image.png

这是某年某月某日,大概是内心很燥热的季节写的一个小工具,一键下载知乎漂亮妹子图片。至于当时的心情现在我只能在写文章的时候,模拟一下了。

开整

八月。湛蓝的天空,悬着火球般的太阳,云彩好似被融化了,消失得无影无踪。没有一丝风,大地活像个大蒸笼。

好热,好烦躁,好无聊。无意间又打开知乎?,首页冒出一个问题给好看的女生拍照是种怎样的体验?,齐刷刷一大摞好看的小姐姐,看的人好生陶醉。作为一个曾经的理工屌丝男,我相信此刻你的想法和我一样,要是可以把她们装进那《学习教程》文件夹就好了。

怎么办?一张张图片右键保存吗?不不不,效率低,鼠标按多了“右手”还疼。差点忘了,我特么是个程序员啊!程序员啊!程序员啊!这种事难道不应该交给程序去干嘛。

"说干就干"

需求

需求很简单:实现自动下载知乎某个帖子所有回答的图片到本地

分析

所以只要知道了以下两点基本就能够完成了。

  1. 图片链接

能够获取帖子下面答题者上传的图片链接,至于所有图片,那就是搜集所有回答者上传的图片链接就可以了

  1. 下载图片

这个暂时猜想是使用成熟的库,我只需要传入图片链接地址,以及图片下载到哪个目录就可以完成下载。如果没找着这样的库,就只能研究原生的nodejs如何做了。

获取图片链接

我们打开chrome浏览器的控制台,发现页面一打开的时候会有很多个请求发出,但是有一个带"answers"请求很可疑,是不是它负责返回答题者的答案呢?

image.png

在验证这个想法之前,我们先不去看这个请求的具体响应内容。我们先点击一下页面上的查看全部 948 个回答按钮,如果猜的对,"answers"请求应该会再次发出,并且返回答题者的答案。

image.png

点击按钮之后发现,“answers”确实再次发出了,并且查看其响应内容大体如下

image.png
{data: [{// xxx// 答题者的信息author: {// ...},// 答题内容content: '"就是觉得太美好了呀<br><br><figure><noscript><img data-rawheight="1080" src="https://pic4.zhimg.com/v2-a7da381efb1775622c497fb07cc40957_b.jpg" data-rawwidth="720" class="origin_image zh-lightbox-thumb" width="720" data-original="https://pic4.zhimg.com/v2-a7da381efb1775622c497fb07cc40957_r.jpg"></noscript></figure>',// 帖子描述question: {}// xxx 等等},{/// xxx}],paging: {// 是否结束is_end:false,// 是否是刚开始is_start:false,// 查看下一页内容的api地址next: "https://www.zhihu.com/api/v4/questions/49364343/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=5&offset=8&sort_by=default",// 上一页内容的api地址previous: "https://www.zhihu.com/api/v4/questions/49364343/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=5&offset=0&sort_by=default",// 总回答数totals: 948}
}

从响应中我们拿到总的回答数量,以及当前请求返回的答题者的内容也就是content字段,我们要的图片地址就在noscript标签下的img标签的data-original属性中。所以针对要求1:

我们似乎已经拿到了50%的信息,还有另一半的信息是,我们如何获取所有答题者的内容?,别忘了刚才的响应中还有paging字段, 可以拿到下一次内容的数据

// 是否结束
is_end:false,
// 查看下一页内容的api地址
next: 'https://www.zhihu.com/api/v4/questions/49364343/answers?include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&limit=5&offset=8&sort_by=default"',
// 总回答数
totals: ''

query请求部分总共有三个参数

{include: 'xxxx', // 这个参数可能是知乎后台要做的各种验证吧offset: 3, // 页码limit: 5, // 每页内容数量sort_by: 'default' // 排序方式
}

所以看起来,咱们把offset设置为0,limit设置为totals的值,是不是就可以拿到所有数据了呢?尝试之后发现,最多只能拿到20个答题者的数据,所以我们还是根据is_end以及next两个响应值,多次请求,逐步获取所有数据吧。

下载在线图片

针对2. 最后一顿google搜索发现还真有这么一个库request,比如要下载一张在线的图片到本地只需要些如下代码

const request = require('request)request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png'))

到这里1和2两个条件都具备了,接下来要做的就是撸起来,写代码实现了。

预览

在说代码实现之前,我们先来看一下实际下载效果和基本使用吧!!!

使用

require('./crawler')({dir: './imgs', // 图片存放位置questionId: '34078228', // 知乎帖子id,比如https://www.zhihu.com/question/49364343/answer/157907464,输入49364343即可proxyUrl: 'https://www.zhihu.com' // 当请求知乎的数量达到一定的阈值的时候,会被知乎认为是爬虫(好像是封ip),这时如果你如果有一个代理服务器来转发请求数据,便又可以继续下载了。
})

proxyUrl先不关注,后面会仔细说明这个字段的作用

实现

点击查看crawler.js(https://github.com/qianlongo/node-small-crawler/blob/master/crawler.js)

let path = require('path')
let fs = require('fs')
let rp = require('request-promise')
let originUrl = 'https://www.zhihu.com'class Crawler {constructor (options) {// 构造函数中主要是一些属性的初始化const { dir = './imgs', proxyUrl = originUrl, questionId = '49364343', offset = 0, limit = 100, timeout = 10000 } = options// 非代理模式下请求知乎的原始url默认是 https://www.zhihu.comthis.originUrl = originUrl// 代理模式下请求的实际路径, 这里默认也是https://www.zhihu.com// 当你的电脑ip被封了之后,可以通过代理服务器,请求知乎,而我们是向代理服务器获取数据this.proxyUrl = proxyUrl// 请求的最终urlthis.uri = `${proxyUrl}/api/v4/questions/${questionId}/answers?limit=${limit}&offset=${offset}&include=data%5B%2A%5D.is_normal%2Cadmin_closed_comment%2Creward_info%2Cis_collapsed%2Cannotation_action%2Cannotation_detail%2Ccollapse_reason%2Cis_sticky%2Ccollapsed_by%2Csuggest_edit%2Ccomment_count%2Ccan_comment%2Ccontent%2Ceditable_content%2Cvoteup_count%2Creshipment_settings%2Ccomment_permission%2Ccreated_time%2Cupdated_time%2Creview_info%2Crelevant_info%2Cquestion%2Cexcerpt%2Crelationship.is_authorized%2Cis_author%2Cvoting%2Cis_thanked%2Cis_nothelp%3Bdata%5B%2A%5D.mark_infos%5B%2A%5D.url%3Bdata%5B%2A%5D.author.follower_count%2Cbadge%5B%3F%28type%3Dbest_answerer%29%5D.topics&sort_by=default`// 是否已经是最后的数据this.isEnd = false// 知乎的帖子idthis.questionId = questionId// 设置请求的超时时间(获取帖子答案和下载图片的超时时间目前相同)this.timeout = timeout// 解析答案后获取的图片链接this.imgs = []// 图片下载路径的根目录this.dir = dir// 根据questionId和dir拼接的最终图片下载的目录this.folderPath = ''// 已下载的图片的数量this.downloaded = 0// 初始化方法this.init()}async init () {if (this.isEnd) {console.log('已经全部下载完成, 请欣赏')return}// 获取帖子答案let { isEnd, uri, imgs, question } = await this.getAnswers()this.isEnd = isEndthis.uri = urithis.imgs = imgsthis.downloaded = 0this.question = questionconsole.log(imgs, imgs.length)// 创建图片下载目录this.createFolder()// 遍历下载图片this.downloadAllImg(() => {// 当前请求回来的所有图片都下载完成之后,继续请求下一波数据if (this.downloaded >= this.imgs.length) {setTimeout(() => {console.log('休息3秒钟继续下一波')this.init()}, 3000)}})}// 获取答案async getAnswers () {let { uri, timeout } = thislet response = {}try {const { paging, data } = await rp({ uri, json: true, timeout })const { is_end: isEnd, next } = pagingconst { question } = Object(data[0])// 将多个答案聚合到content中const content = data.reduce((content, it) => content + it.content, '')// 匹配content 解析图片urlconst imgs = this.matchImg(content)response = { isEnd, uri: next.replace(originUrl, this.proxyUrl), imgs, question }} catch (error) {console.log('调用知乎api出错,请重试')console.log(error)}return response}// 匹配字符串,从中找出所有的图片链接matchImg (content) {let imgs = []
let matchImgOriginRe = /<img.*?data-original="([^"\?]*?)[?"]/gcontent.replace(matchImgOriginRe, ($0, $1) => imgs.push($1))return [ ...new Set(imgs) ]}// 创建文件目录createFolder () {let { dir, questionId } = thislet folderPath = `${dir}/${questionId}`let dirs = [ dir, folderPath ]dirs.forEach((dir) => !fs.existsSync(dir) && fs.mkdirSync(dir))this.folderPath = folderPath}// 遍历下载图片downloadAllImg (cb) {let { folderPath, timeout } = thisthis.imgs.forEach((imgUrl) => {let fileName = path.basename(imgUrl)let filePath = `${folderPath}/${fileName}`rp({ uri: imgUrl, timeout }).on('error', () => {console.log(`${imgUrl} 下载出错`)this.downloaded += 1cb()}).pipe(fs.createWriteStream(filePath)).on('close', () => {this.downloaded += 1console.log(`${imgUrl} 下载完成`)cb()})})}
}module.exports = (payload = {}) => {return new Crawler(payload)
}

源码实现基本上很简单,大家看注释就可以很快明白。

ip被封了

正当我用写好的crawler.js下载多个帖子下面的图片的时候,程序报了一个这个提示。

系统检测到您的帐号或IP存在异常流量,请进行验证用于确认这些请求不是自动程序发出的"

完蛋了,知乎不让我请求了???

完蛋了,知乎不让我请求了???

完蛋了,知乎不让我请求了???

折腾了半天,最后被当做爬虫给封了。网上找了一些解决方法,例如爬虫怎么解决封IP?

基本上是两个思路

1、放慢抓取速度,减小对于目标网站造成的压力。但是这样会减少单位时间里的抓取量。

2、第二种方法是通过设置代理IP等手段,突破反爬虫机制继续高频率抓取。但是这样需要多个稳定的代理IP。

image.png

继续用本机并且在ip没有发生变化的情况下,直接请求知乎是不可能了,不过我们可以尝试一下2.使用代理服务器。

突然想起自己去年买了一个服务器。就只放了一个resume-native程序。

虽然没法做到像上面两张图一样,哪个代理服务被封,及时再切换另一个代理服务器。但是至少可以通过代理服务器再次下载图片,撸起来。。。

代理IP继续跑

image.png

代理程序proxy.js运行在服务器上,会监测路径为/proxy*的请求,请求到来的时候通过自己以前写的请求转发httpProxy中间件去知乎拉取数据,再返回响应给我们本地。用一张图表示如下

image.png

所以我们原来的请求路径是(为了简化把include这个很长的query参数去除了) https://www.zhihu.com/api/v4/questions/49364343/answers?offset=3&limit=5&sort_by=default

经过代理服务器后变成了(其中xxx.yyy.zzz可以是你自己的代理服务器的域名或者ip加端口) https://xxx.yyy.zzz/proxy/api/v4/questions/49364343/answers?offset=3&limit=5&sort_by=default

这样我们间接地绕过了知乎封ip的尴尬,不过这只是临时方案,终究代理服务器也会被封ip。

················ 执鸢者简介 ·················

大家好,我是执鸢者,毕业于华中科技大学,新时代农民工,现在是百度前端研发工程师,著有《前端百题斩》、数十篇学习思维导图(go、React、Redux、Vue、Vuex、操作系统、Linux、设计模式、js、webpack、nginx)以及大量前端进阶文章,大量同学已通过号主的系列内容获取心仪的offer,关注我获取海量资料、交流工作心得并进卧虎藏龙交流群。

识别方二维码加我微信、拉你进交流

[1] 五万字前端面试宝典

[2] 纯CSS实现beautiful按钮

[3] 一张思维导图入门React

[4] 一文搞定Diff算法

[5] 16张图入门Nginx

[6] 好记性不如烂笔头——Vue3.0篇

[7] 好记性不如烂笔头——Vuex篇

[8] 好记性不如烂笔头——Linux篇

[9]好记性不如烂笔头——React篇

[10] 好记性不如烂笔头——Redux篇

将10000张妹子图片存起来,很棒相关推荐

  1. 爬了10000张NASA关于火星探索的图片,我发现了一个秘密

    前言 最近,我使用爬虫技术,爬取了美国航空航天局,也就是你电影里经常见到的 NASA, 火星探索的相关图片,有 10000 张吧. 嗯嗯,小事情,小事情. 完事儿之后,有点小激动,于是就有了这篇文章, ...

  2. python相似图片识别_Python+Opencv识别两张相似图片

    Python+Opencv识别两张相似图片 在网上看到python做图像识别的相关文章后,真心感觉python的功能实在太强大,因此将这些文章总结一下,建立一下自己的知识体系. 当然了,图像识别这个话 ...

  3. python美女源代码_单身程序员,每晚用python抓取百万张美女图片,连女友都不想找了...

    每当夜深人静时,这位长期单身的程序员就会起床开电脑,然后用python抓取百万张美女图片,存进U盘,目的目前还不知道,但技术是万能的,这样一来,可能连找女朋友的钱都省了. 其实,还有更好看的! 而且还 ...

  4. java gif合成_java图片处理——多张图片合成一张Gif图片并播放或Gif拆分成多张图片...

    1.多张jpg图合成gif动画 /*** 把多张jpg图片合成一张 *@parampic String[] 多个jpg文件名 包含路径 *@paramnewPic String 生成的gif文件名 包 ...

  5. 使用C++实现多张BMP图片转换为YUV动画----附加淡入淡出转场(逐渐变明变暗),及垂直滑像转场(逐行渐变)

    使用C++实现多张BMP图片转换为YUV动画----附加淡入淡出转场(逐渐变明变暗),及垂直滑像转场(逐行渐变) 一.BMP图像简介 1.BMP图像是什么? 2.BMP图像文件结构 1)图象文件头 2 ...

  6. [css] 如何使用css3实现一个div设置多张背景图片?

    [css] 如何使用css3实现一个div设置多张背景图片? background-image:url("1.jpg"),url("2.jpg"),url(&q ...

  7. 名校博士被撤销学位,只因7行文字抄袭及1张互联网图片​……

    近日,日本筑波大学的一名毕业生Takuma Hara被撤销了博士学位,起因是学校调查发现其论文中有7行抄袭的文字和一张从互联网上提取的图片,且没有注明来源. >>>> Taku ...

  8. 仿造小红书页面代码html,jQuery仿小红书登录页,背景图垂直循环滚动登录页,向上循环滚动的动画,实现一张背景图片的无缝向上循环js滚动...

    jQuery仿小红书登录页,背景图垂直循环滚动登录页,向上循环滚动的动画,实现一张背景图片的无缝向上循环js滚动 先看效果图: 图片是gif看着有点卡顿,网页里面其实很流畅的 此代码使用CSS3动画实 ...

  9. 桌面上的计算机图片怎么复制,怎么把一张普通的图片复制到EXCEL表格中

    怎么把一张普通的图片复制到EXCEL表格中以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 怎么把一张普通的图片复制到EX ...

最新文章

  1. stm32--对固件库的认识2
  2. IOS学习之多线程(9)--NSOperation简单介绍
  3. L1-036 A乘以B
  4. python数字信号处理应用中文pdf_人邮新书 Python数字信号处理应用 Python在DSP中应用教程 Python基础 Pytho...
  5. 《看聊天记录都学不会Python到游戏实战?太菜了吧》(7)我用函数写了个特洛伊木马
  6. 嵌入式linux文件系统
  7. 【转】 UML各种线的含义
  8. 打印有向图的强连通分量-----kosaraju算法(最简单的实现)
  9. 第一章: 当前主流的小型嵌入式 GUI
  10. J2EE是什么(一)
  11. Substrate源码分析:启动流程
  12. 要依赖于抽象,不要依赖于具体
  13. 苹果系统使用svg 动画_为什么要使用SVG图像:如何为SVG设置动画并使其快速闪电化
  14. CSDN自动回复灌水乐园帖子-httpClient篇
  15. 图像处理之角点检测与亚像素角点定位
  16. Draemon 360开源的基于Promtheus的升级版本告警系统
  17. 达摩院2021十大科技趋势:云原生重塑IT技术体系
  18. 了解下depends
  19. 高分辨率卫星影像能看到什么?
  20. 猿创征文|小而巧的API文档生成工具之smart-doc

热门文章

  1. 兼容IE6,IE7,IE8,friefox,chreom浏览器圆角及渐变效果
  2. 开仓风险计算器.xlsx(可计算:名义价值、最大资金亏损、开仓所需保证金、开仓资金杠杆、最小逐仓保证金、U本位需开张数、币本位需开张数)
  3. 宾馆管理系统开题报告范文
  4. 仿苹果的全局浮动按钮——swift
  5. 标准英语写作——从中式英语到地道英语——读书笔记2
  6. vue自定义指令的使用
  7. python爬取图解_20行PYTHON代码爬取微博高清大图,小白练手绝佳案例
  8. 苹果6如何截屏_苹果升级iOS14,轻点背面能开启截屏功能,真是太方便了
  9. android 模拟器 绝地求生,绝地求生全军出击电脑模拟器怎样设置?绝地求生全军出击安卓模拟器配置教程...
  10. 搜狗快捷键和eclipse冲突