我想弄清楚为什么我的一个地图调用无效。我正在建立一个爬行器,目的是学习Clojure。
(use '[clojure.java.io])
(defn md5
"Generate a md5 checksum for the given string"
[token]
(let [hash-bytes
(doto (java.security.MessageDigest/getInstance "MD5")
(.reset)
(.update (.getBytes token)))]
(.toString
(new java.math.BigInteger 1 (.digest hash-bytes)) ; Positive and the size of the number
16)))
(defn full-url [url base]
(if (re-find #"^http[s]{0,1}://" url)
url
(apply str "http://" base (if (= \/ (first url))
url
(apply str "/" url)))))
(defn get-domain-from-url [url]
(let [matcher (re-matcher #"http[s]{0,1}://([^/]*)/{0,1}" url)
domain-match (re-find matcher)]
(nth domain-match 1)))
(defn crawl [url]
(do
(println "-----------------------------------\n")
(if (.exists (clojure.java.io/as-file (apply str "theinternet/page" (md5 url))))
(println (apply str url " already crawled ... skiping \n"))
(let [domain (get-domain-from-url url)
text (slurp url)
matcher (re-matcher #"<a[^>]*href\s*=\s*[\"\']([^\"\']*)[\"\'][^>]*>(.*)</a\s*>" text)]
(do
(spit (apply str "theinternet/page" (md5 url)) text)
(loop [urls []
a-tag (re-find matcher)]
(if a-tag
(let [u (nth a-tag 1)]
(recur (conj urls (full-url u domain)) (re-find matcher)))
(do
(println (apply str "parsed: " url))
(println (apply str (map (fn [u]
(apply str "-----> " u "\n")) urls)))
(map crawl urls)))))))))
(defn -main
"I don't do a whole lot ... yet."
[& args]
(crawl "http://www.example.com/"))
首次调用地图有效:
(println (apply str (map (fn [u] (apply str "-----> " u "\n")) urls)))
(println (apply str (map (fn [u] (apply str "-----> " u "\n")) urls)))
但第二个电话似乎被忽略了。
(map crawl urls)
函数正在按预期工作,正在刷新网址,使用正则表达式解析crawl
标记以获取href并且a
中的累积按预期工作,但是我使用loop
和map
调用了crawl
,并且忽略了对urls
的调用。
此外,如果我尝试拨打map
,则会再次忽略此呼叫。
我几周前开始了我的Clojure冒险,所以任何建议/批评都是最受欢迎的。
谢谢
答案 0 :(得分:2)
在Clojure中,map
很懒惰。从文档中,映射:
返回一个惰性序列,该序列由将f应用于的结果组成 每个coll的第一项的集合,然后将f应用于集合 每个coll中的第二个项目,直到任何一个colls 累。
您的抓取功能是一种带副作用的功能 - 您spit
- 将某些结果发送到文件,然后println
报告进度。但是,因为map
返回一个懒惰的序列,所以这些事情都不会发生 - 结果序列永远不会被明确地实现,所以它可以保持懒惰。
有许多方法可以实现延迟序列(例如使用map
创建),但在这种情况下,您希望使用具有副作用的函数迭代序列,最好使用doseq
:
反复执行身体(可能是副作用) 绑定和过滤由“for”提供。不保留 序列的头部。返回nil。
如果您将(map crawl urls)
的来电替换为(doseq [u urls] (crawl u))
,则应获得所需的结果。
注意:您对地图的第一次调用按预期工作,因为您使用(apply str)
实现了结果。如果不评估序列,就无法(apply str)
。