有没有办法让 EWW 使用 cURL 来抓取网页?

同一个网页,比如 The Python Tutorial — Python 3.11.1 documentation ,EWW 有时加载得出、偶尔也会加载不出。一方面可能是我的网络有问题,另一方面怀疑 Emacs 自带的 url.el 也不是非常稳定。不知道能不能用 cURL 来作为 EWW 的后端?再看看情况是不是就会有好转。

w3m.el 不行吗?


另外这个网页在墙外用 eww 很快就能加载出来,应该是网络问题。

看了下 eww 函数:

(defun eww (url)
  ...
  (url-retrieve url 'eww-render
		(list url nil (current-buffer))))

实现一个 curl 版本的 url-retrieve 就可以了吧。不知道用命令和模块哪种方式更靠谱。这里还有个基于 curl 的 request.el

为什么 url.el 坚持 elisp 实现,如果说 curl 不是 gnu 的项目,可 wget 是呀。

另外,看到 gnus 提供了 wget/lynx/curl 这么多选项,是因为 url.el 的问题吗?

w3m 不是不行,只是我压根就没用过它,无论是命令行版还是 Emacs 版的。EWW 是 Emacs 自带的,cURL 也是系统自带的,它俩要是能配合起来用最好不过了。

感觉实现起来并不非常容易:首先这个函数是异步的;其次不只是函数参数,一些变量 (如 url-request-method)也会影响它。

url.el 的实现确实有些眼花缭乱,前段时间刚好稍微看了一下,不过现在已经不记得了,只在笔记中找到这段乞丐版的 url-retrieve-synchronously 实现(基于异步的 url-retrieve):

#+BEGIN_SRC emacs-lisp :results value
(require 'url)

(defun get-content (url)
  (let ((async-buffer nil))
    (url-retrieve
     url
     (lambda (&rest ignored)
       ;;; 异步回调
       ;; 在 org 下如果直接 (print (buffer-string)) 只能输出到 *Messages*
       ;; 因为代码片段退出之后才能到达这里
       ;; 如果想输出到 #+results 则必需等待异步操作完成
       (setq async-buffer (current-buffer))))
    (while (null async-buffer)
      ;;; 简单粗暴的等待
      (sleep-for 1))
    (with-current-buffer
        async-buffer
      (buffer-string))))

(print (get-content "http://gnu.org"))
#+END_SRC

上面的代码跑题了,我只是想吐槽一下 url.el。如果封装一个命令行的异步 curl 也不会很复杂,我找到一个现成的 async-shell-command-to-string

#+BEGIN_SRC emacs-lisp :results value
(require 'cl)

(defun async-shell-command-to-string (command callback)
  "Execute shell command COMMAND asynchronously in the
  background.
Return the temporary output buffer which command is writing to
during execution.
When the command is finished, call CALLBACK with the resulting
output as a string.
Synopsis:
  (async-shell-command-to-string \"echo hello\" (lambda (s) (message \"RETURNED (%s)\" s)))
"
  (lexical-let
      ((output-buffer (generate-new-buffer " *temp*"))
       (callback-fun callback))
    (set-process-sentinel
     (start-process "Shell" output-buffer shell-file-name shell-command-switch command)
     (lambda (process signal)
       (when (memq (process-status process) '(exit signal))
         (with-current-buffer output-buffer
           (let ((output-string
                  (buffer-substring-no-properties
                   (point-min)
                   (point-max))))
             (funcall callback-fun output-string)))
         (kill-buffer output-buffer))))
    output-buffer))

;; 测试
(async-shell-command-to-string
 "curl http://gnu.org"
 (lambda (s)
   ;; 结果输出到 minibuffer
   (message s)))
#+END_SRC

这里回调函数的参数只有一个,比 (defun eww-render (status url &optional point buffer encode) 少,不过应该也不难得到这些参数。

比较麻烦的是会话状态吧,但是我看 (eww) 这个函数里并没有 url- 之类的变量,也许只重写这个函数影响不大?我没用过 eww.el。

更无痛的方法应该是改写 url.el 底层的接口了,这里看到有个 curl-for-url.el,它直接替换了 url-http

1 个赞