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

前言

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. npm你不知道的8个快捷键
  2. 干货:Wireshark使用技巧-显示规则
  3. Java Spring源码研究之BeanNameUrlHandlerMapping
  4. 查询计算机专业及选修了英语的学生,实验五 数据库综合查询(学生)
  5. dos和linux有关系吗,DOS和Linux近年来的发展比较
  6. Coarse-Grained lock 粗粒度锁
  7. Tomcat优化实践——网站运维
  8. 毕业设计——房屋租赁管理系统
  9. 1080 端口被占用
  10. 计算机税率函数,excel怎么设置税率 | 如何用EXCEL函数,做一个税金计算表格
  11. HTML面试题七:b标签和strong标签,i标签和em标签的区别
  12. 利用Navicat Premium导出数据库表结构信息至Excel
  13. STM32用SWD口烧录程序导致锁死
  14. 关于客户端下载文件而不是在服务器生成文件
  15. linux 软件安装及卸载
  16. 网络系统设计的基本原则(一)
  17. Parallel WaveGan论文和代码笔记
  18. 红心大战安卓单机版_红心大战手机版 v1.30 安卓版
  19. 腾讯云从业者培训课程介绍/腾讯云介绍
  20. 海天味业组建智能生产线,打造完美海天酱油

热门文章

  1. linux内核升级图文攻略(转)
  2. 20万元内超值真香:纯电SUV零跑C11越级满配上市
  3. 响铃:被带偏的智能家居,如何才能“逃出生天”
  4. 6.8 使用迷你图功能在单元格中插入图表 [原创Excel教程]
  5. 超级简单的Android镂空文本方法
  6. Laravel快速建站
  7. 三星N900刷机包 力卓ROM V2.3 多窗口全开 ROOT 在线主题
  8. CDGA认证考试含金量这么高?
  9. 互联网服务端技术——如何学(上)
  10. [Python]二叉树中序遍历代码以及思路