最近想申请个域名玩玩儿, 有不少网站提供域名检索服务, 比较有名的有godaddy, namecheap, name等. 但是想到一个搜一下太麻烦了. 有的网页做得也不是很友好, 没法指定检索自己感兴趣的顶级域名(TLD, Top Level Domain), 如.com
, .io
.
发现某网站的API是直接暴露在外的, 可以直接GET
到. 那索性写个脚本把自己感兴趣的都爬一下得了.
为了保护该网站, 该API地址以API_URL
指代.
该API的设计是:
// Request
GET: API_URL?domain=a.com,b.io
// Response
{
status: [
{ name: "a.com", available: false },
{ name: "b.io", available: true }
]
}
请求的domain部分是逗号分隔的域名. 返回结果我略去了其他细节, 只保留核心部分.
代码
代码见的Github.
这东西其实不复杂, 不值得作为一个Repo, 更适合作为Gist. 但是Gist的版本控制感觉比较弱, 适合比较小的代码片段. 我以后也许会在现在的代码上做修改, 就还是建了个Repo.
我的python写得很少, 可能比较蹩脚. 欢迎吐槽.
使用
命令行里面输入python domain-scanner.py
即可.
接受三个参数, start
, endings
和interval
.
start
代表你想从哪个域名开始查找, 如想从abc.com
开始查找, 就--start=abc
. 默认值为a
.
endings
代表你感兴趣的TLD. 如你对com
和io
感兴趣, 就--endings=com,io
. 默认值为com
interval
代表间隔多久发一个请求. 如你想2秒发一次, 就--interval=2
, 默认值为1
.
示例: python domain-scanner.py --start=abc --endings=com,io --interval=2
.
另外你还可以python domain-scanner.py --help
查看帮助信息.
示例:
$ python domain-scanner.py --start=abc --ending=com,io --interval=1
start=abc, endings=com,io, interval=1
from: abc, to: acp, available: []
from: acq, to: aed, available: []
from: aee, to: afr, available: ['aez.io', 'afq.io', 'aev.io']
from: afs, to: ahf, available: ['afz.io', 'agq.io', 'afv.io', 'ahe.io', 'agj.io']
from: ahg, to: ait, available: ['ahw.io', 'aij.io', 'ahz.io', 'ahj.io', 'ahy.io']
from: aiu, to: akh, available: ['ajy.io', 'ajh.io', 'ajd.io', 'ajq.io']
from: aki, to: alv, available: ['alh.io', 'alq.io', 'akw.io', 'akq.io', 'akz.io']
from: alw, to: anj, available: []
from: ank, to: aox, available: ['aor.io', 'aoq.io', 'anl.io']
...
注:
- 4个字母(包含)以下的
.com
域名都不是available
的 - 我发现有些不
available
不代表被别人注册了, 也可能是你需要花大价钱出offer.
知识点
GET + JSON
发GET
请求并把结果解析为JSON.
from urllib.request import urlopen
import json
data = json.load(urlopen(url))
字符串加一操作
本代码里我只考虑英文字母的情况, 从指定的start
域名开始向后遍历, 相当于26进制的数字.
python里面字符和int
之间的转换要通过chr
和ord
两个函数来做. 其中, chr
的作用是, 将数字转换成字符, 如chr(97) == 'a'
; ord
则反过来.
每次字符串加一都要来回来去chr
和ord
我觉得有些麻烦. 所以写了两个函数.
def stringify(s):
return ''.join([chr(ord("a") + ch) for ch in s])
def codify(s):
return [ord(ch) - ord("a") for ch in s]
codify
的作用是把字符串转化为数字数组(a
对应0
), 如codify("abc") === [0,1,2]
; stringify
则反过来. 这样我对数组直接进行加一操作简单一些.
def next(a):
carry = 1
index = -1
while -index <= len(a) and carry:
a[index] += 1
carry = a[index] // 26
a[index] %= 26
index -= 1
if carry:
a.insert(0, 0)
这样, 用户输入字符串如"abc", 我就先codify
成[0,1,2]
, 然后内部都对数字数组进行next
操作, 当给用户显示的时候再stringify
即可.
Python3整除
我发现Python3里面1/2
等于0.5
. 默认是浮点数除法. 想整除咋办? 用a // b
即可
参考: Python integer division yields float
Python的setInterval
JS写多了, 发现Python居然连setInterval
都没有.
需要用threading.Timer
进行一下封装.
def setInterval(func, sec):
def funcWrapper():
setInterval(func, sec)
func()
t = threading.Timer(sec, funcWrapper)
t.start()
return t
这只是最基础的封装, 不支持cancel
. 更详细的见参考: Python Equivalent of setInterval()?
另外, 要往调用函数里面传参咋整? 没想到好办法, 这次简单用全局变量来做的.
Python读取命令行参数
直接让人修改代码里面的参数再用好像太麻烦了. 开点命令行参数让用户自己设置变量吧? 怎么做?
参考了这篇文章Command Line Arguments in Python, 不过只用到了前面的sys.argv
和getopt
. 刚刚发现后面还有Python3的简便argparse
模块... 下次再说吧.
简单例子如下:
import sys, getopt
unixOptions = "sh"
gnuOptions = ["start=", "help"]
try:
arguments, values = getopt.getopt(sys.argv[1:], unixOptions, gnuOptions)
for arg, val in arguments:
if arg in ("-s", "--start"):
start = val
elif arg in ("-h", "--help"):
help = True
except getopt.error as err:
print("Failed to parse arguments.", str(err))
sys.exit(2)
其中定义了两个命令行参数start
和help
, 其中, start=
表示start
接收参数, 而help
不接收.
Python print flush
OK, 现在脚本支持命令行参数了, 赶紧去试试, 结果又踩坑.
我在Sublime或者CMD里面运行脚本, print
的内容都能够正常输出, 但是发现在Git Bash里面输出不出来. 查了一下, 这种情况下需要手动flush.
两种方案:
- 运行脚本的时候加上
-u
, 如python -u domain-scanner.py
. -
print
函数调用的时候指定flush=True
. 如print("yo", flush=True)
.
我采用了第二种方案.
参考: How to flush output of Python print?
429
发送到一段时间后, 开始有一些请求返回HTTP Error 429
. 查了一下这个代表Too Many Requests
. 嗯, 发得太快了. 这也是为什么加入了interval
这个参数.
目前对于失败的请求并没有失败重传, 只打印了个fail. 因为想想如果重传也一样会导致后面的请求数增多, 然后后面更加拥挤. 不如记下失败的那些, 然后之后整体重发.