# Python 图片爬虫

# 0x00 前言

在平时抓取部分自己喜欢的资源的时候,我们常常会去下载一些比较有用的资源,比如,我比较喜欢 GEM 的照片,但是,这个东西,总不能总是去找别人要吧,那么,怎么办?

很简单,我们只需要通过 Python 写一个小小的爬虫就可以解决这些问题。

什么是爬虫?自己可以百度去。

我这里指的爬虫是那些可以模拟浏览器的行为的小程序。

比如,我要抓取 G.E.M 的相片,那么,我就想个办法。把图片的地址解析出来。然后写一个小功能下载不就好了么。

虽然,话是这么说,

但,怎么下手?

这在写这篇文章,并且写到这里的时候,刚刚决定了抓取几个站点

(刚刚百度了 邓紫棋壁纸 得到这个网站 http://www.6188.com/show/12788_1.html)

# 0x01 准备工作以及爬取思路

Python3,Chrome 浏览器或者 Firefox,
Python3 基本依赖库 beautifulsoup4 lxml

任务如下:

  1. 第一个简单案例 http://www.6188.com/show/12788_1.html

  2. 百度 API 解析

  3. 虾米照片爬取

4.instagram 墙外下载 Gem 照片

  1. 发烧级别的 GEM 粉丝 - 虾米网 down! down! down!

涉及到的知识点:

  1. 爬虫的最最基本思路
  2. 几个解析方法 正则解析,bs4 解析,lxml 解析
  3. 多线程使用

# 0x02 6188.com 壁纸抓取 – 关键词爬虫

先说一下思路,首先,你要会点击下载按钮.(#-#)

  1. 访问 http://www.6188.com/show/12788_1.html

  2. 点击下载大图

  3. 看大图,手动另存为

这是普通人下载图片的方式。

让我们用程序员的眼光来看。

浏览器呈现的具体的过程可以看我的 PyDjango 中关于计算机 Http 协议的部分。

经过抓包 (http 包)分析 (chrome 的 F12), 知道,要想获取图片原始链接,有这么一个流程

http://www.6188.com/show/12788_1.html 解析出下面链接

http://www.6188.com/show.php?pic=/flashAll/20140211/1392111065nvjKS7.jpg 解析下面链接

http://pic.6188.com/upload_6188s/flashAll/20140211/1392111065nvjKS7.jpg 下载图片。

这样一看,非常简单明了。这就是下载一张图片的链接。

同样道理,把下面的程序写成一个 for 循环,就可以直接下载 35 张图片。

http://www.6188.com/show/12788_1.html http://www.6188.com/show/12788_2.html
http://www.6188.com/show/12788_3.html ... http://www.6188.com/show/12788_35.html

写的应该比较容易认出

import os
import re
import requests

__author__ = 'micheal'

r = requests.get("http://www.6188.com/show/12788_1.html")
m = re.search("(/show\.php.+jpg)\"", r.text)

pic_url = "http://www.6188.com" + m.group(0)
print(m.group(0))
data = requests.get(pic_url)
print(data.text)

m = re.search("src='(http://.+\.jpg)", data.text)
real_url = m.group(1)

try:
    print("real_url-- 正在下载 --"+real_url)
    r = requests.get(real_url,stream=True)
    fileName = "GEM.jpg"
    fileFullPath = os.path.join('/home/micheal/Pictures/', fileName)
    print("正在下载" + str(data.status_code))
    with open(fileFullPath, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024 * 2):
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
    pass
except Exception:
    print("出错")
    raise

print("任务完成")

添加 For 循环,优化一下

import os
import re
import requests

__author__ = 'micheal'

s = requests.session() # 仿真 browser 使用一个会话

for i in range(1,30):
    html_url = "http://www.6188.com/show/12788_"+str(i)+".html"
    r = s.get(html_url)
    print("downloading" + html_url)
    m = re.search("(/show\.php.+jpg)\"", r.text)

    pic_url = "http://www.6188.com" + m.group(0)
    print(m.group(0))
    data = s.get(pic_url)
    print(data.text)

    m = re.search("src='(http://.+\.jpg)", data.text)
    real_url = m.group(1)

    try:
        img_store_dir = "/home/micheal/Pictures/GEM/6188"
        print("real_url-- 正在下载 --"+real_url)
        r = s.get(real_url,stream=True)
        fileName = "GEM"+str(i)+".jpg"
        if not os.path.exists(img_store_dir):
            os.makedirs(img_store_dir)
        fileFullPath = os.path.join(img_store_dir, fileName)
        print("正在下载" + str(data.status_code))
        with open(fileFullPath, 'wb') as f:
            for chunk in r.iter_content(chunk_size=1024 * 2):
                if chunk: # filter out keep-alive new chunks
                    f.write(chunk)
                    f.flush()
        pass
    except Exception:
        print("出错")
        continue
    finally:
        pass

print("任务完成")

###评价

第一个小程序应该是非常容易看懂的。

那么,这个程序有什么缺点呢?

  • 下载速度太慢,需要使用多线程,
  • 解析方法不具有通用性,这张网页中只有一个地址需要解析,所以正则表达式还是可以胜任。但是,复杂的网页肯定不行
  • 下载的图片太少了。

# 0x03 百度 API 的解析 – 关键词多线程

主要就是增加了一个多线程的任务,原理什么的基本上和上面那个的相似

import json
import os
from queue import Queue
import re
import threading
import requests

__author__ = 'micheal'

q = Queue(maxsize=0)
# http://image.baidu.com/i?tn=resultjsonavatarnew&ie=utf-8&word=%E9%82%93%E7%B4%AB%E6%A3%8B&cg=star&pn=0&rn=60

s = requests.session()

def worker():
    while True:
        try:
            real_url = q.get()
            print("正在下载" + real_url)

            img_store_dir = "/home/micheal/Pictures/GEM/baidu"
            print("real_url-- 正在下载 --"+real_url)
            r = s.get(real_url,stream=True)
            fileName = real_url.split("/")[-1]
            if not os.path.exists(img_store_dir):
                os.makedirs(img_store_dir)
            fileFullPath = os.path.join(img_store_dir, fileName)
            print("正在下载" + str(r.status_code))
            with open(fileFullPath, 'wb') as f:
                for chunk in r.iter_content(chunk_size=1024 * 2):
                    if chunk: # filter out keep-alive new chunks
                        f.write(chunk)
                        f.flush()
            pass
        except Exception as e:
            print("出错")
            raise e
            continue

        finally:
            pass

        q.task_done()

    pass

if __name__ == "__main__":

    for i in range(100):
        fr = i * 60
        to = i * 60 + 60
        r = requests.get("http://image.baidu.com/i?tn=resultjsonavatarnew&ie=utf-8&word=%E9%82%93%E7%B4%AB%E6%A3%8B&cg=star&pn="+str(fr)+"&rn="+str(to)+"&itg=1&z=3&fr=&width=0&height=0&lm=-1&ic=0&s=0&st=-1")
        data = json.loads(r.text)

        for j in range(60):
            real_url = data['imgs'][j]['objURL']
            print(real_url)
            q.put(real_url)

        for j in range(20):
            t = threading.Thread(target=worker)
            t.daemon = True
            t.start()

q.join()

print("任务完成")

引入了多线程,但是抓取效果并不好,大概有 10% 左右的照片可能是有点问题的,把线程数目从 20 条调整小一些。

先写到这里,明天接着写剩下来的代码。

# 0x04 抓取虾米的相册 – 反反爬虫

好吧,我们将魔手伸向了虾米音乐的图片板块

http://www.xiami.com/artist/pic-55712

我们尝试使用昨天的方法获取页面。

ss = requests.session()
r = ss.get(“http://www.xiami.com/artist/pic-55712?spm=0.0.0.0.IaKt5o&page=3)
print(r.text)

但是出现问题了,

<!DOCTYPE HTML PUBLIC-//IETF//DTD HTML 2.0//EN”>
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor=”white”><script>
with(document)with(body)with(insertBefore(createElement(“script”),firstChild))setAttribute(“exparams”,”category=&userid=&aplus&yunid=&&asid=AABI4v5USkJh1pUp01o=,id=”tb-beacon-aplus”,src=(location>”https”?//s”:”//a”)+”.tbcdn.cn/s/aplus_v2.js”)
</script>

<h1>400 Bad Request</h1>
<p>Your browser sent a request that this server could not understand. Sorry for the inconvenience.<br/>
Please report this message and include the following information to us.<br/>
Thank you very much!</p>
<table>
<tr>
<td>URL:</td>
<td>http://www.xiami.com/artist/pic-55712?spm=0.0.0.0.IaKt5o&amp;page=3</td>
</tr>
<tr>
<td>Server:</td>
<td>web-xiami-main-030.cm10</td>
</tr>
<tr>
<td>Date:</td>
<td>2015/03/10 20:23:36</td>
</tr>
</table>
<hr/>Powered by Tengine</body>
</html>

Process finished with exit code 0
</td>
</tr>
</table></p></h>
</script></title></head>
</html>

我们前面使用的代码都是爬取一些没有做太多防止爬虫的网站,但是,我们今天准备爬取的是一个有防护措施的网站。

没办法,修改一下 headers, 然后继续访问即可。

# 0xEE 更新


ChangeLog:

  • 2017-12-19 重修文字