用common lisp写网站之一

 

       前段时间一直在用易读看天涯社区的连载小说,但是更新得太慢,往往天涯上面晚上更了,易读第二天早上才刷出来。严重怀疑是手动更新的。技痒,决定自己写一个易读网。原理很简单——启动一个线程定时抓取指定帖子的网页文本,通过正则表达式提出出有用的信息,并:(1)存储入数据库备用(2)格式化输出为网页,实现只看楼主的功能。

      先用Python写了一个小脚本,实现了抓取网页,正则表达式提取内容和格式化输出的功能,服务器也是使用python自带的服务器。寥寥不到100行。写完后感觉不是很爽,感觉Python也不过如此。遂决定用一直心仪已久的common lisp来重写。

      基本环境如下:惠普笔记本+win7操作系统。lisp环境是clisp for windows。

     考虑到需要进行web编程,clisp需要一大堆七七八八的库。在这个网址发现了很多好东西:http://weitz.de/   。一个德国老教授的主页,很多实用的lisp包都在里面。

     其中,hunchentoot用来做服务器兼框架,cl-ppcre用来进行正则分析,drakma用来抓取网页内容。

     首先按照http://blog.csdn.net/albert_lee/article/details/5953369安装quicklisp这个不错的包管理器。然后将以下这一坨单独整理为一个文件init.lisp,以加载相关的库:

   

(ql:quickload "hunchentoot")
(ql:quickload "drakma")
(ql:quickload "cl-ppcre")
再到clisp的命令行下面

 

在接下来的web编程中就能调用这三个包里面的函数了。


    现在把生成网页的代码,单独放在getpage.lisp里面。每次用vim修改完代码,使用clisp的命令行重新load一下就行了。

第一步,当然是抓取网页文本。两步搞定:

(setq	entry	"http://bbs.tianya.cn/post-16-798295-1.shtml")
(setq	text	(drakma:http-request entry))

    第一行代码,指定了入口地址。首先我们由简入繁,只分析一个帖子的第一页。entry就是这第一页的url。 drakma:http-request函数只需要一个参数,也就是需抓取的网页的url。返回值就是抓取的内容,现在把它保存进text里面。

    接下来,要获取四大信息:各层楼的内容,各层楼的发布时间,各层楼的作者,各层楼的楼号;还有整个帖子的发布作者。参考德国老教授的主页里关于cl-ppcre的文档,跌跌撞撞地使用了正则表达式搞定:

;;匹配各层楼的内容       
(setq sections (cl-ppcre:all-matches-as-strings "((?<=div class=\\\"bbs-content\\\">).*(?=</div>)|(?<=div class=\\\"bbs-content clearfix\\\">).*(?=</div>))" text))
;;匹配各层楼的发布时间
(setq times (cl-ppcre:all-matches-as-strings "(?<=replytime=\\\")([^\\\"]+)" text))
;;匹配各层楼的作者
(setq authors (cl-ppcre:all-matches-as-strings "(?<=author=\\\")([^\\\"]+)" text))
;;匹配各层楼
(setq layers (cl-ppcre:all-matches-as-strings "(?<=class=\\\"bbs-layer\\\">)([^<]+)" text))
;;匹配标题
(setq page-title (concatenate 'string 
                  "<BR><CENTER><font face=\"微软雅黑\" size=\"4\">" 
                  (car (cl-ppcre:all-matches-as-strings "(?<=<title>)[^_]*" text))
                  "</font></CENTER><BR><BR>"))
       需要注意的地方有:使用all-matches-as-strings匹配出来的结果是一个列表,列表的每个元素是一个字符串。所以打印的时候要注意进行相应的处理,不然会出现莫名其妙的“(”和“)”号。page-title的代码部分使用了car来取出匹配标题结果列表的第一个也是唯一一个元素也就是标题。除了page-title是一个字符串以外,sections, times, authors和layers都是列表。并且这几个列表的长度是相同的。

      现在需要这么一个列表,每个列表的元素是一个字符串,每个字符串对应于我们的网站的每一楼。也就是说每个字符串必须包含内容,发言人,发言时间,楼层,必要的html标记等等。而且,最重要的,只能是楼主的发言内容才能出现。

;;将每一段加上字体和段落标记
(setq sections 
	(let ((index 0) (master (car authors)))
		(mapcar #'(lambda(x)
			(progn
				;;构造段落
				(setq x 
                   (if (equal master  (nth index authors))   
                        (concatenate  'string 
					    "<BR><CENTER><font face=\"微软雅黑\" size=\"2\" color=\"#333388\">"  
					    (if (equal index 0) "" (nth (- index 1) layers)) " " (nth index times) " " (nth index authors) "</font></CENTER><BR>"
					    "<div class=\"style1\"><font face=\"微软雅黑\" size=\"2\">"  x  "</font></div>")
                        ""))
				(incf index)
				x)) 
			    sections)))
        这个函数有点复杂,使用了映射函数和闭包。总的来讲主要就是在原来的sections列表的基础上进行处理。前面已经说过,sections, times, anthors和layers表的长度都是相同的。保存了一个页面上所有楼层的不同方面的信息。那么,可以这样来进行过滤——逐个查看authors列表(每个元素是一层楼的作者名字的字符串),如果其第N个元素不是楼主的名字(楼主的名字是该表的第0个元素),那么其他几个表的第N个元素也舍弃;否则就把几个列表的N号元素和一堆的HTML标记组合起来,形成新的N号元素。

       所有的新元素组成的列表,又一次性地放入sections表里面。这时新的sections表,与旧的sections表长度同样。但内容已经不同了。旧的sections表每个元素对应于每层楼的正文部分,而新的sections表的每个元素则表现为——其要么是上面组合来的一大坨东西(楼主层)以求直接输出为网页,要么是一个“”字符串(非楼主层)以不输出任何东西。

       要输出网页,还需要构造“头”和“尾巴”,其中头部还包括了简单的CSS样式表。这里使用了比较粗糙的方法:    

;;构造头部
(setq page-head  
	"<HTML><HEAD><TITLE>第1页</TITLE></HEAD><BODY>
        <style>div.style1 {
            margin-top:0;
            margin-left:200;
            margin-right:200;
            margin-bottom:0;
            padding-top:30;
            padding-left:50;
            padding-right:50;
            padding-bottom:50;
            background-color:#eeeeee;
        }</style>")

;;构造尾部
(setq page-tail "</BODY></HTML>")    
前面得到的sections只是中间部分。生成页面时,把上面生成的段落列表放在这两者直接结合成新的列表,再一次性输出出来即可。

(hunchentoot:define-easy-handler (greet :uri "/zhikanlouzhu") ()
  (format nil "~{~A~}" `(,page-head ,page-title ,@sections ,page-tail)))


;;启动服务器
(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 8080))
在format语句里面,·(,page-head ,page-title ,@sections ,page-tail)生成了一个新的列表。注意最左边的是一个反引号,也就是ESC键下方那个键。~{~A~}是一个迭代指令,用于迭代输出一个列表里面的所有元素。其他部分参考hunchentoot的文档。http:.//localhost:8080可以访问到本机上的主页。"/zhikanlouzhu"则指定了我们生成的网页的下级目录。下面启动服务器的函数不会自动返回。所以在clisp里面load入该文件后,会一直运行而不会出现新的提示符。要手动停止可以使用先Ctrl-C再Ctrl-D组合。

现在(1)载入写好的文件:


(2)并通过火狐访问http://localhost:8080/zhikanlouzhu:


      现在可以看到lisp输出的网页了。左上角的λ字样表示这是使用了lisp的东东的服务器在工作~~~~








  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值