Python爬虫: 带你上车之爬取妹子图

简介

30行python轻松爬取成百上千的妹子图到本地。没时间解释了,快上车。

什么是爬虫?

网络爬虫,顾名思义就是在网上爬来爬去的“虫子”,它能够按照一定规则自动抓取网络数据的脚本。比如说你找到了一个特别棒的网站,上面全是妹子图。而你想把它们存到你的随身硬盘当中。如果你要一张一张保存的话那需要比较持久的耐力,这个时候你就需要通过爬虫来帮你抓取你心心念念的妹子图。

那么如何通过爬虫来完成任务呢?

运行机制

其实爬虫的工作流程和人是一样的,都需要经过下面几个步骤:

使用本机的IP连接到网络 ->使用地址登入网站 ->看到网页内容 ->筛选需要的信息 -> 保存下载 -> 登入新网页 ->重复之前的动作

是不是非常相似?

为什么使用python

很多编程语言都可以写爬虫,可我们为什么选择python呢?总的来说就是四个字:简单够用

  • Python语法简单,开发效率高
  • Python 有着丰富第三方爬虫工具库(requests,scrapy,BeautifulSoup)
  • 爬虫的速度瓶颈大多是在网络阻塞上,非超大规模爬取很少遇到计算性能瓶颈
  • Python起初被用来开发搜索引擎,所以关于爬虫的资料很多,社区活跃

让我们开始吧!

首先先创建一个后缀为.py的python文件(名字自己想.py)

工具准备

由于这次只是一个简单的小项目,我们并不需要使用第三方库,我们需要的只有python3

  • Python3
  • urllib.request
    熟悉python2的对urllib库一定不陌生,我们要用的是其中的urlopen方法
  • re(正则表达式)
    正则表达式是根据一定规则来匹配相应字符串,从网页中提取我们需要的内容
  • time 设定休眠时间,减慢爬取速度,减少对方服务器的压力
  • gzip 对于那些使用网页压缩技术的网站,我们需要将它解压

来看我们第一段代码,在我们的文件开头导入需要的工具

1
2
3
4
import urllib.request
import re
import time
import gzip

接下来我们就需要使用urllib库来登入网站

使用urllib读取网页内容

为了准备这个教程,我不(hou)辞(yan)劳(wu)苦(chi)地找来了优妹子来作为我们今天要爬的网站。(真的是为了教学),在下载妹子的图片之前,我们需要先分析通过网站的源代码来找出我们需要的图片链接。可能你没有学过HTML,看不懂网页的源代码,但是没关系,我们要做的事情有一半浏览器替我们做了,剩下的一半就是找!规
!律!

我们知道爬虫会增加对方服务器的压力,有的时候如果对方发现你使用的爬虫而不是用户的话,就会切断连接导致爬取中断(如果没有断点续传功能就等于失败), 所以我们需要将我们的爬虫看起来更像用户一样。当然爬虫和反爬虫这里的内容太多这里不会做过多讲解,在这里我们需要给我们的爬虫添加header的信息,因为有些服务器会对请求的header做检查:

1
header = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'}

是不是很多东西很眼熟?对了,我们发送这段请求让服务器知道我们是一个用户在使用Windows NT 6.1(就是win7) 的Firefox浏览器来浏览网页,为了不让代码看起来特别乱,我们先将它保存在一个变量中,

接着我们要把网站使用字符串的形式保存在变量url中:

1
url = "http://www.youmzi.com"

使用urllib.request的Request方法来向对方服务器发送请求,格式为(网址,[参数..]),将我们的header变量作为headers参数传递进去。

1
requests = urllib.request.Request(url,headers = header)

接着使用urlopen方法打开网页(刚才请求返回的结果)

1
opened = urllib.request.urlopen(requests)

读取我们的网页内容(源代码)并解码

1
content = opened.read().decode('gbk')

这里我们使用read()方法来进行读取并在后面添加decode方法对输出结果进行解码,不同网页使用不同的编码标准,一般来说使用utf8格式,但是我们在源代码的前几行发现写着,这是不一样的编码方式。但是当我们使用 decode(‘gb2312’)并不管用。你灵机一动,想到了GBK,这是一种非常常用的中文编码格式,于是就有了上面那行代码

这个时候你再试图print出来content的内容,得到的就是网页源代码,当你使用浏览器的时候,右键点击网页也会出来检查源代码的选项。这就是我们获取的内容,说明你已经成功连接到了网站

但是这一堆乱七八糟的字符让我怎么找到妹子图呢

别着急,我们要进行非常重要的步骤,网页解析

使用正则表达式

正则表达式简介

正则表达式是一种使用特定字符来匹配字符串的模式,它可以让我们在不确定内容的情况下进行模糊匹配。

正则表达式的语法内容很多,如果想要了解更多请点击前面的链接或自行搜索。但是本着”一招在手,天下我有“的精神,我们使用经典的“.*?”来进行匹配。你可能猛一看这是什么鬼,这可是我们找到妹子图的关键法宝,其中:

  • ‘.’ 代表了任意字符
  • ‘*‘ 代表了匹配无限次
  • ‘?’ 代表了使用非贪婪模式,尽可能少的进行匹配
  • () 有括号的会被提取,无括号的只会被用来匹配不会提取

举个栗子,在’内容‘这个字符串当中我们只需要匹配开头,结尾,内容两边的标志,并且使用括号标志我们需要提取的内容就可以了。

1
<.*?>(.*?)<.*?>

变成人话就是

<管他是什么>管他是什么我要了<管他是什么>

构建我们的表达式

怎么样很简单吧,现在我们就需要对网页源代码进行解析,回到浏览器,右键点击一张妹子图,然后点检查(chrome)/审查元素(Safari)。你会看到一个窗口显示网页的源代码,高亮的部分是所选内容的代码,将鼠标移动到不同的代码上,网页中会用阴影部分表示出你当前代码所展示的内容,我们来右键点击检查一张图片:

1
<img src="http://ymz.qqwmb.com/allimg/c160926/14JY6111Q560-L3G6_lit.jpg" border="0" width="160" alt="美媛馆 [MyGirl] 2016.09.12 VOL.225 xxxxxx">

其中jpg所标记的那个链接就是我们要的链接,但是我们不能只用双引号匹配,因为双引号内包含的内容不只有链接,所以我们尽量多描述一点来让我们的匹配更加精准。

1
<img src="(.*?)".*?>

这样就好了嘛,还没有。img是图片标签,网站上那么多图片,你不能把网站的广告logo什么都抓下来吧,这时候你就需要移动你的鼠标找规律,在保持单个完整性的同时多向外部拓展一层,你匹配的就更准确。比如现在在img标签,外面有个a标签,鼠标放上去也指向图片,a标签外面是li标签,还是指向图片,li外面是div标签,还是..不,这次指向很多图片了,所以我们应该使用图片外面的li标签。我们来看代码

1
<li><a href="http://www.youmzi.com/12255.html" title="尤果网 UGirls  Vol.205 香川颖 日系美女" target="_blank"><img src="http://ymz.qqwmb.com/allimg/c160922/14J54TECK0-c4X8_lit.jpg" border="0" width="160" alt="尤果网 UGirls  Vol.205 香川颖 日系美女" /></a><p><a href="http://www.youmzi.com/12255.html" title="尤果网 UGirls  Vol.205 香川颖 日系美女" target="_blank"> 尤果网 UGirls  Vol.205 </a> </p></li>

头都大了,这什么啊。不要惊慌,我们发现又一个规律:除了img标签外,a,li,p标签都是

1
<li><a></a><p></p></li>

这个样子的,有头有尾。这样以来我们就找到头,尾和我们要的内容,然后把其他的模糊匹配掉,得到了

1
<li>.*?<img src="(.*?)".*?</li>

正则表达式就是这么神奇。

调用re模块

有了表达式,我们就需要使用开头导入的re模块来进行解析,首先用re.compile把解析方法存入变量:

1
repattern = re.compile(r'<li>.*?<img src="(.*?)".*?</li>',re.S)

接着使用re.findall来根据方法从源代码提取出来需要的内容

1
girls_link = re.findall(repattern,content)

其中repattern是方法,content是我们刚刚得到的源代码,这个时候re.findall会把所有匹配到的内容放到一个列表当中并且储存到girls_link这个变量:

[妹子图链接1,妹子图链接2 ,........]

到目前为止,我们已经可以找到这一页中所有妹子图的链接了,接下来我们需要储存到本地。

储存到本地

储存的过程就很简单了,由于我们有多个链接,我们需要使用for循环来遍历列表里的所有链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#文件名计数器
girl = 0
for each in girls_link:
#创建文件,文件名从零开始的数字,格式为jpg,写入方法为'wb'二进制写入
a = open(str(girl)+'.jpg','wb')
#使用urllib访问网页并读取内容
b = urllib.request.Request(each,headers =header)
c = urllib.request.urlopen(b)
e = c.read()
#将内容写入文件
a.write(e)
print("No. %d Girl downloaded"%girl)
#计数器+1,进行下一次
girl += 1
#暂停一秒钟,人为降低速度
time.sleep(1)

这样你就可以发现和你的.py文件一起突然多出了好多图片文件,程序默认把内容保存到当前目录下。注意在上面的循环中我插入了一条print语句,这样一来方便了你日后debug需要防止死循环,二来免得你看到光标不动以为死机了,可以追踪进度。没什么事尽量降低爬取速度,不要浪费对方服务器资源。

Gzip网页解压

一般来讲,到这里我们的网页内的图片就爬取好了,但是不巧,我们刚好碰到一个具有网页压缩技术的网站。是不是发现下载下来的图片是损坏的?那是因为在爬取过程中我们没有对内容进行解压。

Gzip是一种常见的数据压缩工具,通常用来压缩文件,后被引入网页压缩技术当中。很多时候当我们不能从网站上抓到正确的数据时,我们应该检查该网站是否使用了压缩技术,简单的方法有使用站长工具的Gzip检测

要解压网站,我们需要在开头导入gzip模块

import gzip

然后将urlopen返回的内容进行解压,再读取就能获得正常的数据

1
2
3
4
5
6
7
8
9
for each in girls_link:
a = open(str(girl)+'.jpg','wb')
b = urllib.request.Request(each,headers = {"Accept-Encoding": "gzip"})
c = urllib.request.urlopen(b)
d = gzip.GzipFile(fileobj = c)
e = d.read()
a.write(e)
print("No. %d Girl downloaded"%girl)
girl += 1

所以现在它可以称得上是一只爬虫了吗,not yet.

网页跳转

不会爬的爬虫不能叫爬虫,爬虫具有一定的网页跳转能力。可以自动地移动到新的页面才能进行大规模地数据爬取。对于点进来看这篇文章的你们,显然一页的图片并不能满足你们嘿嘿嘿嘿嘿。。

我们来看首页,首页只展示了一部分图片,并没有预期中的2,3,4..分页页码出现。但是我们看到有个’更多妹子图’可以点击,点击之后,页面跳转到

http://www.youmzi.com/xg/

完全没有头绪,但是事实上第一页的页码通常被隐藏,所以我们需要进入下一页,

http://www.youmzi.com/xg/list_10_2.html

再下一页:

http://www.youmzi.com/xg/list_10_3.html

是不是找到了什么规律?我们试着用这个规律来返回到第一页:

http://www.youmzi.com/xg/list_10_1.html

没错,我们成功返回到了第一页,同时验证了第一页的页码通常被隐藏的真理。我们找到了规律,就可以按套路在外面加一个循环,首先先把我们前面的url变量从首页改为

url = "http://www.youmzi.com/xg/list_10_%d.html"%page

page就是我们的要爬的页面数字,初始值我们设为1,然后可以使用input来设定上限作为循环条件,这里我们使用while循环会更简单

1
2
3
4
5
6
7
8
pages = int(input("Please enter the pages you want: "))
page = 1
girl = 0
while page <= pages:
url = "http://www.youmzi.com/xg/list_10_%d.html"%page
requests = urllib.request.Request(url,headers =header)
.....
...

要注意的是,刚才在for循环那里设置的girl= 0一定要放在while前面,否则爬取图片的时候,第二页会覆盖第一页的内容。

再用函数包装一下,一个简单的抓妹子图的脚本就出来了

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import urllib.request
import re
import time
import gzip

def youmeizi():
header = {
'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'
}
girl = 0
pages = int(input("Please enter the pages you want: "))
girls_basket = []
page = 1
while page <= pages:
url = "http://www.youmzi.com/xg/list_10_%d.html"%page
requests = urllib.request.Request(url,headers =header)
opened = urllib.request.urlopen(requests)
content= opened.read().decode('gbk')
repattern = re.compile(r'<li>.*?<img src="(.*?)".*?</li>',re.S)
girls_link = re.findall(repattern,content)
for each in girls_link:
a = open(str(girl)+'.jpg','wb')
b = urllib.request.Request(each,headers = {"Accept-Encoding": "gzip"})
c = urllib.request.urlopen(b)
d = gzip.GzipFile(fileobj = c)
e = d.read()
a.write(e)
print("No. %d Girl downloaded"%girl)
girl += 1
time.sleep(1)

youmeizi()

最后再次重申一下,在练习爬虫的过程当中。尽量要做一个温柔的人,温柔对待服务器的人:

  • 在练习爬虫的的时候,爬个几页十几页成功了就行,如果只是练习,没有必要几百页几百页地爬,造成对方服务器资源浪费。
  • 在时间宽松的情况下,尽量添加sleep减少对方服务器压力
  • 需要大规模爬的时候,尽量避开高峰期,在晚上服务器压力小的时候爬取可以避免对方服务器高负载。

况且那么多妹子图,

看得过来嘛