Tcl 4 Web: 用 http 获取网站内容

Tcl 自带有一个 http package 实现了 HTTP 客户端协议。可以用于访问网页,抓取数据等操作。

客户端的 HTTP

客户端的 HTTP 协议,可以简单理解为

  1. 向服务器发送一个请求
  2. 从服务器接收数据

同步 http::geturl

package require http

set token [http::geturl http://www.bing.com/]

# 同步请求,阻塞并等待数据返回

http::status $token       ;# ok
http::code $token         ;# HTTP/1.1 200 OK
http::ncode $token        ;# 200
http::size $token         ;# 119637

http::meta $token         ;# header dict
http::data $token         ;# body content

异步 http::geturl

proc http_done {token} {
  upvar #0 $token state

  puts $state(status)

  puts $state(meta)        ;# header
  puts $state(body)        ;# body

  puts $state(type)        ;# mime type from Content-Type
  puts $state(charset)     ;# charset from Content-Type
}

set token [ http::geturl http://www.bing.com/ -command http_done ]

# 异步请求。通过 -command 指定回调函数,或者通过 http::wait 等待数据返回

http::wait $token  ;# 等待响应

GET and POST

# GET
http::geturl http://www.site.com/page?param=value&param2=value2

# POST
http::geturl http://www.site.com/page -query param=value&param2=value2

http::geturl http://www.site.com/page \
  -query [::http::formatQuery param value param2 value2]

# HEAD
http::geturl http://www.site.com/page -validate 1

# PUT
http::geturl http://www.site.com/page -method PUT

数据的读取和保存

默认情况下,不管是请求的返回数据,还是POST的请求数据,都是通过变量直接传递的。

如果需要把数据写入文件——比如下载图片的时候,则可以借助 -channel 实现。

set fout [open image.png "wb"]
http::geturl http://www.site.com/image.png -channel $fout
close $fout

类似的,请求数据——比如上传文件,可以借助 -querychannel 实现。

set fp [open data.json "rb"]
http::geturl http://www.site.com/image.png \
  -type text/json \
  -querychannel $fp
close $fp

Track Progress

如果需要跟踪数据上传(POST)或者下载时的进度,可以指定每次读或写的数据块的大小,和响应的回调函数。

proc http_write_progress {token total current} {
    upvar #0 $token state
}

proc http_read_progress {token total current} {
    upvar #0 $token state
}

http::geturl http://www.site.com/page \
  -querychannel $fp \
  -queryblocksize 1024 -queryprogress http_write_progress \
  -blocksize 1024 -progress http_read_progress

定制 Header

http::config -useragent "Chrome"
http::config -accept "text/json"

http::geturl http://www.site.com/page \
  -headers [list \
    Content-Type text/json \
    X-Remote     1         \
  ]

用长连接 Keep-Alive 提高效率

HTTP/1.1 协议支持 Keep-Alive 连接以提高传输效率。

-keepalive选项用于这个目的,默认是关闭的。

http::geturl http://www.site.com/page -keepalive 1

超时处理 timeout

涉及网络的东西,总有通讯失败的可能。通过 -timeout 选项指定时间。

http::geturl http://www.site.com/page -timeout 6000 ;# 单位是 ms

并行访问

同时发起多个请求——比如同时下载多个图片,可以减少总的数据获取时间。

下面是一个简单的并行访问的例子。实践中虽然代码会更复杂,但基本框架大致如此。

set http_jobs     [dict create]
set http_all_done 0

proc http_done {token} {
  upvar #0 $token state

  puts [list $state(status) $state(body)]

  dict unset ::http_jobs $token     ;# 通过删除对应条目标记为完成
  if {[dict size $http_jobs]==0} {
    set ::http_all_done 1           ;# 触发全部完成的事件(Event)
  }
}

foreach url $url_list {
  set token [http::geturl $url -command http_done]
  dict set ::http_jobs $token 1
}

vwait http_all_done

在这个基础上逐步扩展,可以写出一个简单的爬虫。