list - 根据他们的“汽车”拉出子列表
问题描述
我正在尝试使用 elisp 构建一个网络爬虫,因此我已将目标网站 ( https://weather.naver.com ) 的 HTML 解析为一个巨大的列表。例如,此列表是原始 HTML 的列表片段:
(defvar *test* '(li
((class . "item_time")
(data-tmpr . "2")
(data-wetr-cd . "7")
(data-wetr-txt . "흐림")
(data-is-daytime . "false")
(data-ymdt . "2020121100"))
"
"
(span
((class . "time day"))
"0시")
(i
((class . "ico _cnLazy night")
(data-ico . "ico_wt7"))
(span
((class . "blind"))
"흐림"))
"
"
(span
((class . "blind"))
"2도")
"
"))
car
并想通过他们的 s拉出子列表。例如,我想通过给出和作为输入来提取所有以data-tmpr
from开头的子列表。到目前为止,我已经编写了这段代码:*test*
*test*
'data-tmpr
(defun dotted-list-p (list)
(and (listp list) (not (listp (cdr list)))))
(defun mapdot (func coll)
`(,(funcall func (car coll)) . ,(funcall func (cdr coll))))
(defun mapfunc (tag coll)
(if (listp coll)
(if (equal (car coll) tag)
coll
(if (dotted-list-p coll)
(if (equal (car coll) tag)
coll
'())
'())
(reduce #'append (mapcar (apply-partially #'mapfunc tag) coll) :initial-value '()))
'()))
(defun find-by-tag (parsed-html tag)
(if (equal (car parsed-html) tag)
parsed-html
(mapcar (apply-partially #'mapfunc tag) parsed-html)))
但是,计算以下表达式会遇到错误:
ELISP> (find-by-tag *test* 'class)
*** Eval error *** Wrong type argument: listp, "2"
我对错误消息感到困惑,因为"2"
必须来自点对'(data-tmpr . "2")
,因此递归调用(find-by-tag '(data-tmpr . "2") 'class)
应该nil
因为这个条件表达式而产生:
(if (dotted-list-p coll)
(if (equal (car coll) tag)
coll
'())
'())
为什么评估会(find-by-tag *test* 'class)
导致错误?
编辑:错字。Edit2:更好的标题。
解决方案
与 leetwinski 的回答一样,我不会回答您提出的直接问题,而是退后一步,尝试解决根本问题。
我还将在 CL 中进行此操作(common-lisp
在标签中!):我确信这可以在 elisp 中完成,但是,好吧。
首先,您不是在这里处理一堆 conses:您正在处理一些树结构,这就是您应该处理的结构。该结构恰好被表示为一堆 conses,但唯一关心它的代码应该是进行抽象的代码。
这是此类代码的示例。我使用了错误的名称,因为这实际上是对 HTML(或 XML)的抽象,但我写得很快。
- 一棵树要么是一个节点,要么是一个blob;
- 节点具有名称、属性列表和子节点列表,它们是树;
- blob 不是节点,也没有任何已定义的属性。
- 属于节点的属性具有名称和值。
请注意,当我说“节点具有子列表”时,我的意思是:当您真正想要的是列表时,可以谈论列表。下面是实现这个抽象的代码:
;;; Things in the tree are either blobs or nodes
;;;
(defun treep (thing)
(or (nodep thing) (blobp thing)))
(defun blobp (thing)
(atom thing))
(defun nodep (thing)
(and (consp thing)
(listp (cdr thing))))
;;; Accessors for nodes
;;;
(defun node-name (node)
(assert (nodep node) (node)
"~S isn't a node" node)
(car node))
(defun node-attributes (node)
(assert (nodep node) (node)
"~S isn't a node" node)
(cadr node))
(defun node-children (node)
(assert (nodep node) (node)
"~S isn't a node" node)
(cddr node))
;;; predicate & accessors for attributes
;;;
(defun attributep (thing)
(and (consp thing)
(not (listp (cdr thing)))))
(defun attribute-name (attribute)
(assert (attributep attribute) (attribute)
"~S isnt an attribute" attribute)
(car attribute))
(defun attribute-value (attribute)
(assert (attributep attribute) (attribute)
"~S isnt an attribute" attribute)
(cdr attribute))
;;; Tree constructors (unused below)
;;;
(defun make-node (name &key
(attributes '())
(children '()))
(assert (every #'attributep attributes) (attributes)
"attributes ~S aren't" attributes)
(assert (every #'treep children) (children)
"children ~S aren't" children)
(list* name attributes children))
(defun make-attribute (name value)
(assert (not (listp value)) (value)
"attribute values can't be lists because, sorry")
(cons name value))
请注意,此处的检查数量可能过多。但是,好吧,如果产生这些树的东西把它们搞砸了,我想知道。
所以现在我们对我们正在制作的树有了一个抽象,我们现在可以编写代码来遍历这些树寻找东西,只使用这个抽象。这样做的一个好方法是编写一个walker,它是一个可以为我们遍历树的函数,在它找到的各种东西上调用访问者函数。这是一个简单的实现:请注意,访问者获得了他们上方的节点堆栈,他们可以使用这些节点来知道他们在哪里。
(defun walk-tree (tree &key
(node-visitor nil node-visitor-p)
(attribute-visitor nil attribute-visitor-p)
(blob-visitor nil blob-visitor-p))
;; A simple-minded recursive walker. Visitors get two arguments:
;; the thing they are visiting and the stack (a list) of nodes above
;; it.
(labels ((walk (thing stack)
(cond
((nodep thing)
(let ((new-stack (cons thing stack)))
(when node-visitor-p
;; node visitors want the current stack, not the
;; new one
(funcall node-visitor thing stack))
(when attribute-visitor-p
;; attribute visitors need the new stack so they
;; can know which node they are attributes of
(dolist (a (node-attributes thing))
(funcall attribute-visitor a new-stack)))
(dolist (c (node-children thing))
;; and the new stack is what we pass down
(walk c new-stack))))
((blobp thing)
(when blob-visitor-p
;; blob visitors just want the current stack
(funcall blob-visitor thing stack)))
(t
(error "mutant horror")))))
(walk tree '()))
tree)
我们可以检查一下:
(defun print-tree-stacks (tree)
(walk-tree tree
:node-visitor
(lambda (n s)
(print (mapcar #'node-name
(cons n s)))))
(values))
现在:
> (print-tree-stacks *test*)
(li)
(span li)
(i li)
(span i li)
(span li)
好吧,闻起来不错。请注意,walker 纯粹是根据树抽象来编写的:如果您更改了表示,那么 walker 中的任何内容都不会改变。所以现在我们可以使用这个 walker 来提取我们想要的任何信息。假设我们要查找具有指定名称的属性。
(defun extract-attributes-named (tree name)
(let ((attrs '()))
(walk-tree tree
:attribute-visitor
(lambda (a s)
(declare (ignore s))
(when (eql (attribute-name a) name)
(push a attrs))))
(nreverse attrs)))
我们可以测试一下:
> (mapcar #'attribute-value (extract-attributes-named *test* 'class))
("item_time" "time day" "ico _cnLazy night" "blind" "blind")
诀窍是定义描述你想要做什么的抽象,然后使用这些抽象,而不需要深入到实际的表示中。
推荐阅读
- html - 纯 HTML 和 CSS 客户端表单
- java - 从基类创建 JPA 实体
- reactjs - 服务器端渲染功能如何利用 DB 调用?
- javascript - 如何使用事件监听器退出 selenium-webdriver?
- ios - Swift Firebase 在删除时触发 Cloud Function
- android - 自定义可扩展列表的可能性
- python - AttributeError:“int”对象没有属性“map”
- amazon-web-services - Lambda@Edge 是否会在失败时重试?
- bash - Yad list 获取函数中所选行的值
- python - 我的一个精灵不会出现在我的 pygame 代码中