Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

2020年4月29日11:35:12 27 1,265 °C
摘要

本文男女老少皆宜,什么妹子图、肌肉男,学会了本文的方法,一切尽收囊中!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

一、前言

经过上两篇文章的学习,爬虫三步走:发起请求、解析数据、保存数据,已经掌握,算入门爬虫了吗?

不,还远远不够!只掌握这些,还只能算门外汉级别。

今天,就来带大家继续学习,怎么爬的更优雅

按照惯例,还是从实战出发,今天咱们就爬个图片,盘点那些遇到的问题,和优雅的解决方案。

本文男女老少皆宜,什么妹子图、肌肉男,学会了本文的方法,一切尽收囊中!

二、实战背景

咱不来吸睛劲爆的图片下载,咱来点清淡的家常菜。

动漫之家漫画下载!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

这个实战,你会遇到动态加载初级反爬,会了本文的方法,你还怕爬不到心心念的"美图"吗?

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

三、漫画下载

咱不下载整站资源,就挑一本下载,别给服务器太大压力。

挑来挑去,找了本动漫之家排名靠前的一本《妖神记》,说实话,看了漫画第一章的内容,浓浓的火影气息。

URL:https://www.dmzj.com/info/yaoshenji.html

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

想下载这本动漫,我们需要保存所有章节的图片到本地。我们先捋捋思路:

  • 拿到所有章节名和章节链接
  • 根据章节链接章节里的所有漫画图片
  • 根据章节名,分类保存漫画

看似简单,实际做起来,可能遇到各种各样的问题,让我们一起优雅的解决这些问题吧!

1、获取章节名和章节链接

一个网页,是由很多div元素组成的,比如这个样子。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

不同的div存放不同的内容,如上图,有存放标题Jack Cui的div,有存放菜单的div,有存放正文内容的div,有存放版权信息的div

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

瞧,不难发现,只要拿到class属性为zj_listdiv标签,就能拿到章节名和章节链接,都存放在这个div标签下的a标签中。

再仔细观察一番,你会发现,div标签下还有个ul标签,ul标签是距离a标签最近的标签。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

用上一篇文章讲解的BeautifulSoup,实际上直接匹配最近的class属性为list_con_liul标签即可。编写如下代码:

瞧,章节名和章节链接搞定了!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

没有难度啊?别急,难的在后面。

2、获取漫画图片地址

我们只要分析在一个章节里怎么获取图片,就能批量的在各个章节获取漫画图片。

我们先看第一章的内容。

URL:https://www.dmzj.com/view/yaoshenji/41917.html

打开第一章的链接,你会发现,链接后面自动添加了#@page=1。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

你翻页会发现,第二页的链接是后面加了#@page=2,第三页的链接是后面加了#@page=3,以此类推。

但是,这些并不是图片的地址,而是这个展示页面的地址,要下载图片,首先要拿到图片的真实地址

审查元素找图片地址,你会发现,这个页面不能右键

这就是最最最最低级反爬虫手段,这个时候我们可以通过键盘的F12调出审查元素窗口。

有的网站甚至把F12都禁掉,这种也是很低级的反爬虫手段,骗骗刚入门的手段而已。

面对这种禁止看页面源码的初级手段,一个优雅的通用解决办法是,在连接前加个view-source:

用这个链接,直接看的就是页面源码。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

更简单的办法是,将鼠标焦点放在浏览器地址栏,然后按下F12依然可以调出调试窗口。

这个漫画网站,还是可以通过F12审查元素,调出调试窗口的。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

我们可以在浏览器调试窗口中的Network里找到这个页面加载的内容,例如一些css文件啊、js文件啊、图片啊,等等等。

要找图片的地址,直接在这里找,别在html页面里找,html信息那么多,一条一条看得找到猴年马月。

Network中可以很轻松地找到我们想要的图片真实地址,调试工具很强大,Headers可以看一些请求头信息,Preview可以浏览返回信息。

搜索功能,过滤功能等等,应有尽有,具体怎么用,自己动手点一点,就知道了!

好了,拿到了图片的真实地址,我们看下链接:

https://images.dmzj.com/img/chapterpic/3059/14237/14395217739069.jpg

这就是图片的真实地址,拿着这个链接去html页面中搜索,看下它存放在哪个img标签里了,搜索一下你会发现,浏览器中的html页面是有这个图片链接的。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

但你是用view-source:打开这个页面,你会发现你搜索不到这个图片链接。

记住,这就说明,这个图片是动态加载的!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

使用view-source:方法,就是看页面源码,并不管动态加载的内容。这里面没有图片链接,就说明图片是动态加载的。

是不是判断起来很简单?

遇到动态加载不要慌,使用JavaScript动态加载,无外乎两种方式:

  • 外部加载
  • 内部加载

外部加载就是在html页面中,以引用的形式,加载一个js,例如这样:

这段代码得意思是,引用cuijiahua.com域名下的call.js文件。

内部加载就是Javascript脚本内容写在html内,例如这个漫画网站。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

这时候,就可以用搜索功能了,教一个搜索小技巧。

https://images.dmzj.com/img/chapterpic/3059/14237/14395217739069.jpg

图片链接是这个,那就用图片的名字去掉后缀,也就是14395217739069在浏览器的调试页面搜索,因为一般这种动态加载,链接都是程序合成的,搜它准没错!

不出意外,你就能看到这段代码,14395217739069就混在其中!

看不懂Javascript,怎么办啊?

没关系,说实话,我看着也费劲儿。

那咱们就找找规律,分析分析,看看能不能优雅的解决这个动态加载问题,我们再看这个图片链接:

https://images.dmzj.com/img/chapterpic/3059/14237/14395217739069.jpg

链接中的数字是不是眼熟?

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

这不就是这几个数字合成的吗?

好了,我有个大胆的想法!直接把这些长的数字搞出来,合成下链接试试看。

运行代码,你可以得到如下结果:

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

踏破铁鞋无觅处,得来全不费工夫

比对一下你会发现,这些,还真就是漫画图片的链接!

但是有个问题,这么合成的的图片链接不是按照漫画顺序的,这下载下来漫画图片都是乱的啊!不优雅

这个网站也是人写的嘛!是人,就好办!惯性思维,要是你,是不是小数放在前面,大数放在后面?这些长的数字里,有13位的,有14位的,并且都是以14开头的数字,那我就赌它末位补零后的结果,就是图片的顺序!

程序对13位的数字,末位补零,然后排序。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

在跟网页的链接按顺序比对,你会发现没错!就是这个顺序!

不用读懂Javascript合成链接代码,直接分析测试,够不够优雅

3、下载图片

万事俱备,只欠东风!

使用其中一个图片链接,用代码下载试试。

通过urlretrieve方法,就可以下载,这是最简单的下载方法。第一个参数是下载链接,第二个参数是下载后的文件保存名。

不出意外,就可以顺利下载这张图片!

但是,意外发生了!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

出现了HTTP Error,错误代码是403。

403表示资源不可用,这是又是一种典型的反扒虫手段。

别慌,我们再分析一波!

打开这个图片链接:

URL:https://images.dmzj.com/img/chapterpic/3059/14237/14395217739069.jpg

这个地址就是图片的真实地址,在浏览器中打开,可能直接无法打开,或者能打开,但是一刷新就又不能打开了!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

如果再打开章节页面,再打开这个图片链接就又能看到图片了。

章节URL:https://www.dmzj.com/view/yaoshenji/41917.html

记住,这就是一种典型的通过Referer的反扒爬虫手段!

Referer可以理解为来路,先打开章节URL链接,再打开图片链接。打开图片的时候,Referer的信息里保存的是章节URL。

动漫之家网站的做法就是,站内的用户访问这个图片,我就给他看,从其它地方过来的用户,我就不给他看。

是不是站内用户,就是根据Referer进行简单的判断。

这就是很典型的,反爬虫手段!

解决办法也简单,它需要啥,咱给它就完了。

使用closing方法可以设置Headers信息,这个Headers信息里保存Referer来路,就是第一章的URL,最后以写文件的形式,保存这个图片。

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

下载完成!就是这么简单!

四、漫画下载

将代码整合在一起,下载整部漫画。编写代码如下:

大约40分钟,漫画即可下载完成!

Python3 网络爬虫(三):漫画下载,动态加载、反爬虫这都不叫事!

还是那句话,我们要做一个友好的爬虫。写爬虫,要谨慎,勿给服务器增加过多的压力,满足我们的获取数据的需求,这就够了。

你好,我也好,大家好才是真的好。

五、总结

  • 本文讲解了如何判断页面信息是不是动态加载的,如何解决动态加载问题。
  • 本文讲解了一些常见的反爬虫策略以及解决办法。

微信公众号搜索【JackCui-AI】关注一个在互联网摸爬滚打的潜行者。

weinxin
微信公众号
分享技术,乐享生活:微信公众号搜索「JackCui-AI」关注一个在互联网摸爬滚打的潜行者。
Jack Cui

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

目前评论:27   其中:访客  15   博主  12

    • avatar mrlee 来自天朝的朋友 谷歌浏览器 Windows 10 中国 移动 1

      难度就这么上来了,看的我开始懵逼了

        • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

          @mrlee 跨度有点大吗?哈哈

            • avatar mrlee 来自天朝的朋友 谷歌浏览器 Windows 10 中国 移动 1

              @Jack Cui chapterpic_hou = re.findall(‘\|\|(\d{5})’, str(script_info))[0]
              chapterpic_qian = re.findall(‘\|jpg\|(\d{4})’, str(script_info))[0]
              这里看不懂怎么拿出来那串东西的

                • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

                  @mrlee re正则化匹配的。

            • avatar richardnan 来自天朝的朋友 谷歌浏览器 Windows 10 河南省安阳市 移动 1

              这是view-source:https://www.dmzj.com/view/yaoshenji/76439.html的js代码,你写的re和这个不匹配呢

              var arr_img = new Array();
              var page = ”;
              eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!”.replace(/^/,String)){while(c–){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return’\\w+’};c=1};while(c–){if(k[c]){p=p.replace(new RegExp(‘\\b’+e(c)+’\\b’,’g’),k[c])}}return p}(‘g f=\'{“h”:”i”,”j”:”0″,”e”:”d\\/3\\/2\\/1\\/7.4\\r\\5\\/3\\/2\\/1\\/6.4\\r\\5\\/3\\/2\\/1\\/8.4\\r\\5\\/3\\/2\\/1\\/a.4\\r\\5\\/3\\/2\\/1\\/c.4\\r\\5\\/3\\/2\\/1\\/b.4\\r\\5\\/3\\/2\\/1\\/k.4\\r\\5\\/3\\/2\\/1\\/l.4\\r\\5\\/3\\/2\\/1\\/v.4″,”n”:”9″,”m”:”o”,”p”:”\\q\\s \\t\\x\\u\\y\\w”}\’;’,35,35,’|96148|3059|chapterpic|jpg|nimg|15270380883014|152703808557|15270380901948||15270380938684|15270380989625|15270380964343|img|page_url|pages|var|id|76439|hidden|1527038101934|15270381054133|chapter_order|sum_pages|252|chapter_name|u7b2c174||u8bdd|u56de|uff08|15270381075071|uff09|u5f52|u4e0a’.split(‘|’),0,{}))

              应该还会报错的啊

                • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

                  @richardnan 不就是顺序换了一下吗?正则匹配话,没问题的是以|区分的。

                    • avatar richardnan 来自天朝的朋友 谷歌浏览器 Windows 10 河南省安阳市 移动 1

                      @Jack Cui 这个地址好像要找js带码中的第一个|96148 作为chapterpic_hou 但是你举的例子中前面是两个||,有很多都是一个|,两个||找不到这个96148了。会找到||152703这个。
                      var arr_img = new Array();
                      var page = ”;
                      eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!”.replace(/^/,String)){while(c–){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return’\\w+’};c=1};while(c–){if(k[c]){p=p.replace(new RegExp(‘\\b’+e(c)+’\\b’,’g’),k[c])}}return p}(‘g f=\'{“h”:”i”,”j”:”0″,”e”:”d\\/3\\/2\\/1\\/7.4\\r\\5\\/3\\/2\\/1\\/6.4\\r\\5\\/3\\/2\\/1\\/8.4\\r\\5\\/3\\/2\\/1\\/a.4\\r\\5\\/3\\/2\\/1\\/c.4\\r\\5\\/3\\/2\\/1\\/b.4\\r\\5\\/3\\/2\\/1\\/k.4\\r\\5\\/3\\/2\\/1\\/l.4\\r\\5\\/3\\/2\\/1\\/v.4″,”n”:”9″,”m”:”o”,”p”:”\\q\\s \\t\\x\\u\\y\\w”}\’;’,35,35,’|96148|3059|chapterpic|jpg|nimg|15270380883014|152703808557|15270380901948||15270380938684|15270380989625|15270380964343|img|page_url|pages|var|id|76439|hidden|1527038101934|15270381054133|chapter_order|sum_pages|252|chapter_name|u7b2c174||u8bdd|u56de|uff08|15270381075071|uff09|u5f52|u4e0a’.split(‘|’),0,{}))

                        • avatar Jack Cui Admin Canada 谷歌浏览器 Mac OS X 10_14_4 加拿大

                          @richardnan 哦哦,代码改了,文章忘更新了,你再重新看下,哈哈。

                          • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

                            @richardnan chapterpic_hou = re.findall(‘\|(\d{5})\|’, str(script_info))[0]
                            chapterpic_qian = re.findall(‘\|(\d{4})\|’, str(script_info))[0]

                      • avatar Chauncey 来自天朝的朋友 谷歌浏览器 Windows 10 江苏省南京市 东南大学 1

                        大佬,我发现按14位id大小排序的方法不能保证所有章节图片顺序都正确,(比如我随便试了崩坏3 https://www.dmzj.com/info/benghuai3rd.html 第08话,第10话)。而且,这种顺序问题没有在全彩话上出现过,有没有什么办法能确实拿到真正顺序呢

                          • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Windows 10 北京市 中国电信北京研究院

                            @Chauncey 有的章节顺序不对吗?呃,我没有全测试。那这样的话,就只能解析js怎么写的了,得麻烦一些。

                              • avatar Chauncey 来自天朝的朋友 谷歌浏览器 Windows 10 江苏省南京市 东南大学 1

                                @Jack Cui 我没办法换成用selenium静默打开浏览器的方法抓的图片地址了,但感觉这样的分析速度会降下来一大截,就是因为不会分析js

                                  • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Windows 10 北京市 中国电信北京研究院

                                    @Chauncey 有浏览器那种后台运行的,不用打开页面渲染,速度还行。

                              • avatar 锟斤拷sir 来自天朝的朋友 谷歌浏览器 Windows 10 河南省郑州市 联通 1

                                ‘set’ object has no attribute ‘items’请问大神,出现这种bug怎么办欸。

                                  • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器  Android 10 Redmi K20 Pro Build/QKQ1.190825.002 北京市丰台区 联通

                                    @锟斤拷sir 用了字典?我没这么写~

                                      • avatar 锟斤拷sir 来自天朝的朋友 谷歌浏览器 Windows 10 河南省郑州市 联通 1

                                        @Jack Cui hhhh,解决了解决了。是header打错了。。
                                        大神在你的GitHub上看见你的那个网易云音乐的脚本好牛,能不能出一个教程欸 :neutral:

                                    • avatar sugar 来自天朝的朋友 谷歌浏览器 Windows 10 广东省汕头市 电信 1

                                      大佬,感谢你分享的爬虫教程,想请教一个问题,正则表达式这块看得不是很懂,您能够提供正则表达式相关的教程或者资料吗?

                                        • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器  Android 10 Redmi K20 Pro Build/QKQ1.190825.002 北京市 中国电信北京研究院

                                          @sugar 正则表达式,我这也没有,都是现用现查的。可以看看看菜鸟教程的,我都是拿来当手册用。

                                        • avatar xym 来自天朝的朋友 谷歌浏览器 Windows 10 陕西省西安市 电信 1

                                          Traceback (most recent call last):
                                          File “D:/python/projects/spider/carton_spider.py”, line 64, in
                                          content_size = int(response.headers[‘content-length’])
                                          File “C:\python\lib\site-packages\requests\structures.py”, line 54, in __getitem__
                                          return self._store[key.lower()][1]
                                          KeyError: ‘content-length’

                                          一直报这个错,不知道哪里写错了。。。求解

                                            • avatar xym 来自天朝的朋友 谷歌浏览器 Windows 10 陕西省西安市 电信 1

                                              @xym 虽然把刚才错误的地方注释掉可以运行成功,但是还是不明白为什么报错,求大神解答,qqq

                                                • avatar Jack Cui Admin Canada 谷歌浏览器 Windows 10 加拿大

                                                  @xym 哦哦,那个是因为网站没有返回这个字段,去掉就好了~

                                              • avatar yu 来自天朝的朋友 谷歌浏览器 Windows 10 四川省自贡市 移动 0

                                                其他都能看懂,就是看到正则表达式,开始懵逼了,哈哈哈

                                                • avatar huang 来自天朝的朋友 谷歌浏览器 Windows 10 广东省深圳市 深圳大学 0

                                                  Traceback (most recent call last):
                                                  File “.\spider.py”, line 56, in
                                                  chapterpic_hou = re.findall(‘\|(\d{5})\|’, str(script_info))[0]
                                                  IndexError: list index out of range
                                                  请问这个怎么解决?

                                                    • avatar Jack Cui Admin 来自天朝的朋友 谷歌浏览器 Mac OS X 10_14_4 北京市 百度网讯科技联通节点

                                                      @huang 你这就是没有匹配到啊,修改正则。

                                                    • avatar BU.ZAI 来自天朝的朋友 谷歌浏览器 Windows 7 广东省广州市 电信 1

                                                      这句name = name.replace(‘.’, ”)的意思是替换掉.吗?好像没有.呀!不是很懂这句,大佬可以帮我解释下吗,谢谢大佬~

                                                        • avatar BU.ZAI 来自天朝的朋友 谷歌浏览器 Windows 7 广东省广州市 电信 1

                                                          @BU.ZAI 懂了,去掉的是章节名包含的.