最近刚看完《Clojure程序设计》,书上的例子都能看懂,但要开始用Clojure还是有点不知道怎么下手,想写个小程序练练手。之前用Python写过一个抓取花瓣网美女画板下图片的小爬虫,爬虫过程很简单,也没有涉及登录验证或是反爬虫之类的,用来熟悉新语言非常合适,就有了这个Clojure版的爬虫。

我用Python的实现可以看我之前的博客

1. 分析网站

花瓣网美女画板的地址是:http://huaban.com/favorite/beauty/ ,访问该地址会展示20张美女图片,当浏览到页面底端,浏览器再通过Ajax加载20幅图片。爬虫也分两部分,抓取美女面板首页图片和抓取Ajax加载的图片。

1.1 抓取首页图片

访问 http://huaban.com/favorite/beauty/ 得到的响应如下:

![image_1ar7joqrl1pp618ed1i2sh3rp38m.png-68.3kB][1]

可以看到,花瓣网的页面是js动态生成的,我们可以不用渲染页面,直接分析js数据,找到图片地址信息。分析对比发现,图片的信息在app.page[“pins”]变量中,是一个json字符串。在json字符串中,对应20个字典,每个字典对应一张图片信息,字典结构类似这种

![json](http://images.cnitblog.com/blog/346307/201502/041629528742079.png)

图片地址与”file”下面的”key”中的内容有关。

1.2 抓取下拉刷新图片

对网站分析发现,访问 http://huaban.com/favorite/beauty/ 只会展示20张图片,之后每次浏览到页面底端,都会通过Ajax请求得到20张新的图片。Ajax请求url为”http://huaban.com/favorite/beauty/?ise2bat0&max=832135275&limit=20&wfl=1",其中832135275是页面最后一张图片的`pin_id`值。

在请求头添加:

1
2
3
"Accept" "application/json"
"X-Requested-With" "XMLHttpRequest"
"X-Request" "JSON"

模拟Ajax请求,得到图片的json数据,直接解析json数据得到图片地址信息。

2. 编写爬虫

2.1 初始化项目

使用Leiningen构建项目

1
lein new app clojure-crawle

项目中用到了两个库,data.json 是处理json数据的,http-kit是处理http请求的,在project.clj中添加这两个依赖:

1
2
3
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/data.json "0.2.6"]
[http-kit "2.2.0"]]

2.2 爬取首页图片

  1. 获取花瓣网美女标签下的html页面

    1
    2
    3
    4
    5
    6
    7
    8
    (defn get-home-page
    "请求首页内容"
    []
    (let [options {:timeout 1000
    :as :text}
    url "http://huaban.com/favorite/beauty/"
    {:keys [body]} @(http/get url options)]
    body))
  2. 从html页面中获取图片信息json

    1
    2
    3
    4
    (defn get-image-info
    "从首页内容得到图片信息"
    [page]
    (subs (re-find (re-matcher #"page\[\"pins\"\].*;" page)) 15))
  3. 解析json数据位列表

    1
    2
    3
    4
    (defn str2json
    "将json字符串转换为字典"
    [body]
    (json/read-str body :key-fn keyword))
  4. 下载一张图片

    1
    2
    3
    4
    5
    6
    7
    (defn download-image
    "将图片保存在本地"
    [url file-name]
    (println (str "download " url))
    (with-open [in (io/input-stream url)
    out (io/output-stream file-name)]
    (io/copy in out)))
  5. 下载一页20张图片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    (defn make-image-url
    "根据key值得到图片真实地址"
    [k]
    (str "http://hbimg.b0.upaiyun.com/" k "_fw658"))

    (defn make-file-name
    [file-name]
    (str image-save-path file-name ".jpg"))

    (defn download-images
    "根据json字符串下载图片"
    [jsons]
    (dorun (map (fn [a]
    (let [k (:key (:file a)) file-name (:pin_id a)]
    (download-image (make-image-url k) (make-file-name file-name))))
    jsons))
    (:pin_id (last jsons)))
  6. 美女标签页首页图片的完整下载过程

    1
    2
    3
    4
    5
    6
    7
    (defn download-home-page
    "下载首页图片"
    []
    (-> (get-home-page)
    get-image-info
    str2json
    download-images))

2.3 爬取Ajax加载的图片

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
(defn make-json-request-url
"根据pin值创建Ajax请求url"
[pin]
(str "http://huaban.com/favorite/beauty/?is07k6hx&max=" pin "&limit=20&wfl=1"))

(defn get-more-page
"加载Ajax请求"
[last-pin]
(let [options {:timeout 1000
:as :text
:headers {"Accept" "application/json"
"X-Requested-With" "XMLHttpRequest"
"X-Request" "JSON"}}
{:keys [body]} @(http/get (make-json-request-url last-pin) options)]
body))

(defn download-more
"下载通过Ajax异步加载的图片"
[last-pin]
(-> last-pin
get-more-page
str2json
second
second
download-images))

3. 项目地址

完整的项目我放在了Github上了。大家可以下载下来运行玩一玩。