Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

使用Python快速下载大量妹子图

$
0
0

首先有一个网站 http://www.dbmeinv.com/ ,这个网站是别人从豆瓣社区上抓取的妹子图。既然是初次学习那么肯定要选择这种简单的网页开始喽。

该文章适用于python3,如用于Python2需要做少量更改

0X00 爬虫如何工作

爬虫说白了就是模仿人类的行为从网络上批量获取我们所需要的数据的一种工具,在这里我们所需要的数据就是妹子图了。

网络蜘蛛(Web spider)也叫网络爬虫(Web crawler)[1],蚂蚁(ant),自动检索工具(automatic indexer),或者(在FOAF软件概念中)网络疾走(WEB scutter),是一种“自动化浏览网络”的程序,或者说是一种网络机器人。它们被广泛用于互联网搜索引擎或其他类似网站,以获取或更新这些网站的内容和检索方式。它们可以自动采集所有其能够访问到的页面内容,以供搜索引擎做进一步处理(分检整理下载的页面),而使得用户能更快的检索到他们需要的信息。 ----维基百科

如果我们不用爬虫,而是自己手动的点开那个网页来下载所有的妹子图应该怎么做:首先打开网页,然后找到第一张图片,右键保存下来,再寻找下一张保存下来,往复如此,当本页面所有的妹子图你都下载下来了就找到下一页的按钮跳转到下一页。所以我们编程也是完全按照这个顺序来的,首先获取目标网页的源代码,然后使用正则表达式匹配所有的图片,再逐个下载下来,接下找到进入下一页的方法,重复重复。

了解了工作原理之后下面就可以开工了,这里使用的Python版本是3.6注意参考。

0X01 获取页面源代码

第一步就是获取页面的源代码,我们都知道每个页面都是由页面源码构成的,人类看网页的时候是浏览器将源码渲染成我们喜欢的样子,但是当机器看网页的时候就是直接看源码了,所以我们这里先用Python将网页的源码拔下来。

#!/bin/python # coding=utf-8 from urllib.request import urlopen if __name__ == '__main__': this_url = 'http://www.dbmeinv.com/' # 指定url html = urlopen(this_url).read().decode('utf-8') # 打开url并解码 print(html)

Python中的 urllib 库就是用于网络开发的,这里有一行比较长的代码 html = urlopen(url).read().decode('utf-8') ,意思是说打开this_url这个url然后调用了 read() 方法来获取html代码,再将代码用 utf-8 解码,最后得到的就是人眼可读的html代码了。最后运行结束之后会显示出那个首页的html代码。

0X02 正则匹配图片

现在有了首页代码之后可以从代码里找到所有图片了,众所周知在网络上的图片都是 http://xxxxxxx.jpg 或者 http://xxxxxx.png 等,无非就是 http/https 之间的变化,或者 png/jpg/gif 之间的变化。在页面的源码中我们可以看到每个图片的位置是这样的 <img class="height_min" title="本女子最擅长等待" alt="本女子最擅长等待" onerror="img_error(this);" src="http://ww4.sinaimg.cn/bmiddle/0060lm7Tgy1fcr73xpyvpj30dw0iiq6i.jpg" /> </a> ,而且经观察发现,这个网页中所有的页面都没有多余的图片,那么我们就可以用一些简单粗暴的方案了,本来匹配网页中的图片应该匹配 <img> 标签,但这里因为网页很干净,所以我们可以直接匹配链接。由此可以写出正则表达式 http://\S+.jpg 这样就可以匹配到页面中的所有图片。

接下来就是在代码中如何实现这里,首先要引用一个 import re 库,这个库用来支持Python中的正则表达式。

#!/bin/python # coding=utf-8 from urllib.request import urlopen import re if __name__ == '__main__': url = 'http://www.dbmeinv.com/' html = urlopen(url).read().decode('utf-8') img_re = 'http://\S+.jpg' # 书写正则表达式 img_re = re.compile(img_re) # 编译正则表达式 img_list = img_re.findall(html) # 使用findall()找到页面中所有能匹配的部分 print(img_list)

这里 re.compile() 方法是将正则表达式编译,因为这里的正则表达式需要多次使用,所以编译可以加快运行效率。 img_re.findall(html) 方法就是使用 img_re 这个正则表达式来匹配 html 这个字符串,将所有匹配到的结果放在一个列表中返回。所以这里最后输出的会是那个页面下所有图片的url了。

0X03 保存文件到本地

现在我们已经找到了好多张妹子图,那么怎么把这些妹子图保存到本地呢?其实用一个简单的思路就对了,Python中使用 urlopen() 方法不仅可以打开html页面,还可以打开图片,所以我们将图片打开,再用文件I/O的方式写入到磁盘里面不就好了嘛。

#!/bin/python # coding=utf-8 from urllib.request import urlopen import re if __name__ == '__main__': url = 'http://www.dbmeinv.com/' html = urlopen(url).read().decode('utf-8') img_re = 'http://\S+.jpg' img_re = re.compile(img_re) img_list = img_re.findall(html) count = 0 # 用来给文件计数 for i in img_list: img_net = urlopen(i).read() # 打开网络图片 img_local = open('D:/test/' + str(count) + '.jpg', 'wb') # 打开本地文件 count += 1 # 计数 +1 img_local.write(img_net) # 将网络文件写入到本地 img_local.close() # 关闭本地文件

这里 img_net = urlopen(i).read() 就是打开一个url并读取其中的内容,因为内容是二进制的,所以下面打开本地文件的时候要用二进制写入的模式打开 img_local = open('D:/test/' + str(count) + '.jpg', 'wb') ,这儿用到的 D:/test/ 目录是提前创建好的空目录。下面的 img_local.write(img_net) 就是将网络文件的内容写入到本地,最后关闭本地文件 img_local.close() ,这样就将某一个网页中的图片存到本地了。

这样还是看起来很简陋,毕竟只能下载一个页面的妹子图,并不能满足我们的需求,下面就改进这个方案让它可以下载更多的页面。

0X04 多下载两页

下载第二页第三页就需要研究一下了。普遍来说进入下一页有三种方式:1.从当前页面用正则表达式匹配到'下一页'的url;2.找到第一页第二页第三页不同页面的url并找到规律;3.用抓包工具抓取下一页的http请求,然后用Python实现。这里按难度来说 3 > 1 > 2 。下面来分析一下,首先分析最简单的方案。

先来看一下第一页到第五页的url,可以简单的发现一个规律,url最后的参数 pager_offset 应该表示页数,那我们尝试一下 http://www.dbmeinv.com/?pager_offset=1 正好可以进到首页,那这就简单了,可以直接用这个方法来访问第n页了。

http://www.dbmeinv.com/ http://www.dbmeinv.com/?pager_offset=2 http://www.dbmeinv.com/?pager_offset=3 http://www.dbmeinv.com/?pager_offset=4 http://www.dbmeinv.com/?pager_offset=5

下面将代码贴出来,这样就实现了翻页下载。

#!/bin/python # coding=utf-8 from urllib.request import urlopen import re if __name__ == '__main__': count = 0 # 将计数器放到循环外面,方便计数 for page in range(1, 1000): # 设置循环页数 url = 'http://www.dbmeinv.com/?pager_offset=' + str(page) # 拼装url html = urlopen(url).read().decode('utf-8') img_re = 'http://\S+.jpg' img_re = re.compile(img_re) img_list = img_re.findall(html) for i in img_list: img_net = urlopen(i).read() img_local = open('D:/test/' + str(count) + '.jpg', 'wb') count += 1 img_local.write(img_net) img_local.close() 0X05 同时下载好多页

这样下载确实理论上可以下载下来这个网站的所有妹子图,但是效率太慢了。这里可以用多线程优化一下程序,使得程序可以同时下载n张妹子图,而不是一张一张地下载。如果对多线程不了解可以参考我写的这篇文章。这里就顺便将代码整理了一下,变得可读性更好。

#!/bin/python # coding=utf-8 from urllib.request import urlopen from re import compile import threading import time COUNT = 0 # 分析页面中的url # 传入一个页面的html代码,返回一个包含所有图片url的列表 def get_image_url_from_html(html): my_re = 'http://\S+.jpg' my_re = compile(my_re) return my_re.findall(html) # 下载页面中的图片 # 传入一个存着图片url的列表,将其中的所有url下载下来 def download_page(urls): global COUNT # 图片的全局计数器,还能用来给妹子图文件唯一编号 lock = threading.Lock() # 既然用了多线程,当然要控制一下临界区,这里设置了一把锁 for i in urls: fw = urlopen(i).read() fl = open('D:/getmeizi/' + str(COUNT) + '.jpg', 'wb') fl.write(fw) fl.close() lock.acquire() # 上锁 COUNT += 1 # 在临界区里将全局计数器加一,防止出现不同步的现象 lock.release() # 解锁 print('image: ', COUNT) # 下载完成提示 if __name__ == '__main__': base_url = 'http://www.dbmeinv.com/?pager_offset=' # 基础url,用于拼接完整url page_num = 1 # 页数,用于拼接完整url for i in range(10000): # 这个网站总共没有10000页,所以就相当于是所有页了 try: # 能抛出异常的地方其实只有线程数和图片打开,线程问题下面解决 # 这里主要是为了防止图片打不开,比如404什么的,所以才抓了一下异常 html = urlopen(base_url + str(page_num)).read().decode('utf-8') page_num += 1 urls = get_image_url_from_html(html) # 获取当前页面的所有图片url threading.Thread(target=download_page, args=(urls,)).start() # 开线程,下载 except: pass

这里需要注意一个比较严重的问题,就是最后我在开线程的时候没有控制量,所以很有可能同时跑着很多个线程,但是Python的多线程能力又没有那么好,所以需要对线程数量进行控制。

0X06 节制一点

在控制线程数量的时候,我用了一个非常简单的方法,也就是定义一个全局的线程数变量,每次都在临界区操作这个变量,当变量值大于某一指定值的时候就让程序冷静一会儿(休眠0.3秒)。整个程序代码如下,我们可以愉快开心的下妹子图了

#!/bin/python # coding=utf-8 from urllib.request import urlopen from re import compile import threading import time COUNT = 0 THREADS = 0 # 全局控制的线程数 def get_image_url_from_html(html): my_re = 'http://\S+.jpg' my_re = compile(my_re) return my_re.findall(html) def download_page(urls): global COUNT global THREADS lock = threading.Lock() for i in urls: fw = urlopen(i).read() fl = open('C:/getmeizi/' + str(COUNT) + '.jpg', 'wb') fl.write(fw) fl.close() lock.acquire() THREADS -= 1 # 结束一个线程就 -1 lock.release() print('image: ', COUNT) if __name__ == '__main__': base_url = 'http://www.dbmeinv.com/?pager_offset=' page_num = 1 lock = threading.Lock() for i in range(10000): try: html = urlopen(base_url + str(page_num)).read().decode('utf-8') page_num += 1 urls = get_image_url_from_html(html) threading.Thread(target=download_page, args=(urls,)).start() lock.acquire() THREADS += 1 开始了一个新的线程就 +1 while True: if THREADS >= 35: # 经过测试,对于我的电脑和网络,这里设置为35比较合适 time.sleep(0.3) else: break lock.release() except: pass


Viewing all articles
Browse latest Browse all 9596

Trending Articles