简介
30行python轻松爬取成百上千的妹子图到本地。没时间解释了,快上车。
什么是爬虫?
网络爬虫,顾名思义就是在网上爬来爬去的“虫子”,它能够按照一定规则自动抓取网络数据的脚本。比如说你找到了一个特别棒的网站,上面全是妹子图。而你想把它们存到你的随身硬盘当中。如果你要一张一张保存的话那需要比较持久的耐力,这个时候你就需要通过爬虫来帮你抓取你心心念念的妹子图。
那么如何通过爬虫来完成任务呢?
运行机制
其实爬虫的工作流程和人是一样的,都需要经过下面几个步骤:
使用本机的IP连接到网络 ->使用地址登入网站 ->看到网页内容 ->筛选需要的信息 -> 保存下载 -> 登入新网页 ->重复之前的动作
是不是非常相似?
为什么使用python
很多编程语言都可以写爬虫,可我们为什么选择python呢?总的来说就是四个字:简单够用:
- Python语法简单,开发效率高
- Python 有着丰富第三方爬虫工具库(requests,scrapy,BeautifulSoup)
- 爬虫的速度瓶颈大多是在网络阻塞上,非超大规模爬取很少遇到计算性能瓶颈
- Python起初被用来开发搜索引擎,所以关于爬虫的资料很多,社区活跃
让我们开始吧!
首先先创建一个后缀为.py的python文件(名字自己想.py)
工具准备
由于这次只是一个简单的小项目,我们并不需要使用第三方库,我们需要的只有python3
- Python3
- urllib.request 熟悉python2的对urllib库一定不陌生,我们要用的是其中的urlopen方法
- re(正则表达式) 正则表达式是根据一定规则来匹配相应字符串,从网页中提取我们需要的内容
- time 设定休眠时间,减慢爬取速度,减少对方服务器的压力
- gzip 对于那些使用网页压缩技术的网站,我们需要将它解压
来看我们第一段代码,在我们的文件开头导入需要的工具
import urllib.request import re import time import gzip
接下来我们就需要使用urllib库来登入网站
使用urllib读取网页内容
为了准备这个教程,我不(hou)辞(yan)劳(wu)苦(chi)地找来了优妹子来作为我们今天要爬的网站。(真的是为了教学),在下载妹子的图片之前,我们需要先分析通过网站的源代码来找出我们需要的图片链接。可能你没有学过HTML,看不懂网页的源代码,但是没关系,我们要做的事情有一半浏览器替我们做了,剩下的一半就是找!规 !律!
我们知道爬虫会增加对方服务器的压力,有的时候如果对方发现你使用的爬虫而不是用户的话,就会切断连接导致爬取中断(如果没有断点续传功能就等于失败), 所以我们需要将我们的爬虫看起来更像用户一样。当然爬虫和反爬虫这里的内容太多这里不会做过多讲解,在这里我们需要给我们的爬虫添加header的信息,因为有些服务器会对请求的header做检查:
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中:
url = "http://www.youmzi.com"
使用urllib.request的Request方法来向对方服务器发送请求,格式为(网址,[参数..]),将我们的header变量作为headers参数传递进去。
requests = urllib.request.Request(url,headers = header)
接着使用urlopen方法打开网页(刚才请求返回的结果)
opened = urllib.request.urlopen(requests)
读取我们的网页内容(源代码)并解码
content = opened.read().decode('gbk')
这里我们使用read()方法来进行读取并在后面添加decode方法对输出结果进行解码,不同网页使用不同的编码标准,一般来说使用utf8格式,但是我们在源代码的前几行发现写着,这是不一样的编码方式。但是当我们使用 decode('gb2312')并不管用。你灵机一动,想到了GBK,这是一种非常常用的中文编码格式,于是就有了上面那行代码
这个时候你再试图print出来content的内容,得到的就是网页源代码,当你使用浏览器的时候,右键点击网页也会出来检查源代码的选项。这就是我们获取的内容,说明你已经成功连接到了网站
但是这一堆乱七八糟的字符让我怎么找到妹子图呢
别着急,我们要进行非常重要的步骤,网页解析
使用正则表达式
正则表达式简介
正则表达式是一种使用特定字符来匹配字符串的模式,它可以让我们在不确定内容的情况下进行模糊匹配。
正则表达式的语法内容很多,如果想要了解更多请点击前面的链接或自行搜索。但是本着”一招在手,天下我有“的精神,我们使用经典的“.*?"来进行匹配。你可能猛一看这是什么鬼,这可是我们找到妹子图的关键法宝,其中:
- '.' 代表了任意字符
- '*' 代表了匹配无限次
- '?' 代表了使用非贪婪模式,尽可能少的进行匹配
- () 有括号的会被提取,无括号的只会被用来匹配不会提取
举个栗子,在'
<.*?>(.*?)<.*?>
变成人话就是
<管他是什么>管他是什么我要了<管他是什么>
构建我们的表达式
怎么样很简单吧,现在我们就需要对网页源代码进行解析,回到浏览器,右键点击一张妹子图,然后点检查(chrome)/审查元素(Safari)。你会看到一个窗口显示网页的源代码,高亮的部分是所选内容的代码,将鼠标移动到不同的代码上,网页中会用阴影部分表示出你当前代码所展示的内容,我们来右键点击检查一张图片:
<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所标记的那个链接就是我们要的链接,但是我们不能只用双引号匹配,因为双引号内包含的内容不只有链接,所以我们尽量多描述一点来让我们的匹配更加精准。
<img src="(.*?)".*?>
这样就好了嘛,还没有。img是图片标签,网站上那么多图片,你不能把网站的广告logo什么都抓下来吧,这时候你就需要移动你的鼠标找规律,在保持单个完整性的同时多向外部拓展一层,你匹配的就更准确。比如现在在img标签,外面有个a标签,鼠标放上去也指向图片,a标签外面是li标签,还是指向图片,li外面是div标签,还是..不,这次指向很多图片了,所以我们应该使用图片外面的li标签。我们来看代码
<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标签都是
<li><a></a><p></p></li>
这个样子的,有头有尾。这样以来我们就找到头,尾和我们要的内容,然后把其他的模糊匹配掉,得到了
<li>.*?<img src="(.*?)".*?</li>
正则表达式就是这么神奇。
调用re模块
有了表达式,我们就需要使用开头导入的re模块来进行解析,首先用re.compile把解析方法存入变量:
repattern = re.compile(r'<li>.*?<img src="(.*?)".*?</li>',re.S)
接着使用re.findall来根据方法从源代码提取出来需要的内容
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减少对方服务器压力
- 需要大规模爬的时候,尽量避开高峰期,在晚上服务器压力小的时候爬取可以避免对方服务器高负载。
况且那么多妹子图,
看得过来嘛