howdoi的一个简单分析。

曾经看到过下面的这样一段js代码:

try{
doSth();
}
catch (e){
ask_url = "https://stackoverflow.com/search?q="
window.location.href= ask_url + encodeURIComponent(e)
}

howdoi基本就是把这个流程做成了Python脚本。其基本流程如下:

  • step1:利用site语法组装搜索语句(默认指定搜索stackoverflow网站)
  • step2:利用google搜索接口获取搜索引擎第一页排名第一的连接
  • step3:访问该链接,根据排名从高倒下,提取代码块文本
  • step4:提取到就显示到终端,没有提取到就提示未找到答案

当然,howdoi也作了一些其他的工作:

  • 代理设置
  • 既往问题进行缓存,提高下次查询的速度
  • 查询的目标网站可配置
  • 做成Python script脚本命令,方便快捷
  • 代码高亮格式化输出

更多分析请看代码注释:

!/usr/bin/env python

######################################################
#
# howdoi - instant coding answers via the command line
# written by Benjamin Gleitzman (gleitz@mit.edu)
# inspired by Rich Jones (rich@anomos.info)
#
###################################################### import argparse #用于获取脚本命令行参数
import glob
import os
import random
import re
import requests #用于发送http(s)请求
import requests_cache
import sys
from . import __version__
#用于控制台彩色高亮格式化输出
from pygments import highlight
from pygments.lexers import guess_lexer, get_lexer_by_name
from pygments.formatters.terminal import TerminalFormatter
from pygments.util import ClassNotFound
# 用于网页解析
from pyquery import PyQuery as pq from requests.exceptions import ConnectionError
from requests.exceptions import SSLError # 兼容Python2.x和Python3.x的库
if sys.version < '3':
import codecs
from urllib import quote as url_quote
from urllib import getproxies # 处理unicode: http://stackoverflow.com/a/6633040/305414
def u(x):
return codecs.unicode_escape_decode(x)[0]
else:
from urllib.request import getproxies
from urllib.parse import quote as url_quote def u(x):
return x #设置google搜索url
if os.getenv('HOWDOI_DISABLE_SSL'): # 使用系统环境变量中非SSL的http代替https
SEARCH_URL = 'http://www.google.com/search?q=site:{0}%20{1}'
VERIFY_SSL_CERTIFICATE = False
else:
SEARCH_URL = 'https://www.google.com/search?q=site:{0}%20{1}'
VERIFY_SSL_CERTIFICATE = True
#设置目标问答网站
URL = os.getenv('HOWDOI_URL') or 'stackoverflow.com' #浏览器UA,用于伪造浏览器请求,防止网站对脚本请求进行屏蔽
USER_AGENTS = ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:22.0) Gecko/20100 101 Firefox/22.0',
'Mozilla/5.0 (Windows NT 6.1; rv:11.0) Gecko/20100101 Firefox/11.0',
('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.5 (KHTML, like Gecko) '
'Chrome/19.0.1084.46 Safari/536.5'),
('Mozilla/5.0 (Windows; Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46'
'Safari/536.5'), )
#格式化答案输出
ANSWER_HEADER = u('--- Answer {0} ---\n{1}')
NO_ANSWER_MSG = '< no answer given >' #设置缓存文件路径
XDG_CACHE_DIR = os.environ.get('XDG_CACHE_HOME',
os.path.join(os.path.expanduser('~'), '.cache'))
CACHE_DIR = os.path.join(XDG_CACHE_DIR, 'howdoi')
CACHE_FILE = os.path.join(CACHE_DIR, 'cache{0}'.format(
sys.version_info[0] if sys.version_info[0] == 3 else '')) #获取代理(在国内China尤其有用,不解释)
def get_proxies():
proxies = getproxies()
filtered_proxies = {}
for key, value in proxies.items():
if key.startswith('http'):
if not value.startswith('http'):
filtered_proxies[key] = 'http://%s' % value
else:
filtered_proxies[key] = value
return filtered_proxies def _get_result(url):
try:
return requests.get(url, headers={'User-Agent': random.choice(USER_AGENTS)}, proxies=get_proxies(),
verify=VERIFY_SSL_CERTIFICATE).text
except requests.exceptions.SSLError as e:
print('[ERROR] Encountered an SSL Error. Try using HTTP instead of '
'HTTPS by setting the environment variable "HOWDOI_DISABLE_SSL".\n')
raise e # 获取google搜索结果中的连接
def _get_links(query):
result = _get_result(SEARCH_URL.format(URL, url_quote(query)))
html = pq(result)#用pyquery进行解析
return [a.attrib['href'] for a in html('.l')] or \
[a.attrib['href'] for a in html('.r')('a')] def get_link_at_pos(links, position):
if not links:
return False if len(links) >= position:
link = links[position - 1]
else:
link = links[-1]
return link #代码格式化输出函数
def _format_output(code, args):
if not args['color']:
return code
lexer = None # try to find a lexer using the StackOverflow tags
# or the query arguments
for keyword in args['query'].split() + args['tags']:
try:
lexer = get_lexer_by_name(keyword)
break
except ClassNotFound:
pass # no lexer found above, use the guesser
if not lexer:
try:
lexer = guess_lexer(code)
except ClassNotFound:
return code return highlight(code,
lexer,
TerminalFormatter(bg='dark')) #利用政策匹配判断连接是否是问题
def _is_question(link):
return re.search('questions/\d+/', link) #获取问题连接
def _get_questions(links):
return [link for link in links if _is_question(link)] #获取答案(主要是解析stackoverflow的问答页面)
def _get_answer(args, links):
links = _get_questions(links)
link = get_link_at_pos(links, args['pos'])
if not link:
return False
if args.get('link'):
return link
page = _get_result(link + '?answertab=votes')
html = pq(page) first_answer = html('.answer').eq(0)#第一个答案
instructions = first_answer.find('pre') or first_answer.find('code')#pre和code标签为目标代码块
args['tags'] = [t.text for t in html('.post-tag')] if not instructions and not args['all']:
text = first_answer.find('.post-text').eq(0).text()
elif args['all']:
texts = []
for html_tag in first_answer.items('.post-text > *'):
current_text = html_tag.text()
if current_text:
if html_tag[0].tag in ['pre', 'code']:
texts.append(_format_output(current_text, args))
else:
texts.append(current_text)
texts.append('\n---\nAnswer from {0}'.format(link))
text = '\n'.join(texts)
else:
text = _format_output(instructions.eq(0).text(), args)
if text is None:
text = NO_ANSWER_MSG
text = text.strip()
return text def _get_instructions(args):
links = _get_links(args['query']) if not links:
return False
answers = []
append_header = args['num_answers'] > 1
initial_position = args['pos']
for answer_number in range(args['num_answers']):
current_position = answer_number + initial_position
args['pos'] = current_position
answer = _get_answer(args, links)
if not answer:
continue
if append_header:
answer = ANSWER_HEADER.format(current_position, answer)
answer += '\n'
answers.append(answer)
return '\n'.join(answers) #启动缓存
def _enable_cache():
if not os.path.exists(CACHE_DIR):
os.makedirs(CACHE_DIR)
requests_cache.install_cache(CACHE_FILE) #清除缓存
def _clear_cache():
for cache in glob.glob('{0}*'.format(CACHE_FILE)):
os.remove(cache) # 脚本主函数
def howdoi(args):
#构造查询(主要是把问号删除)
args['query'] = ' '.join(args['query']).replace('?', '')
try:
return _get_instructions(args) or 'Sorry, couldn\'t find any help with that topic\n'
except (ConnectionError, SSLError):
return 'Failed to establish network connection\n' #获取用户输入的命令行参数
def get_parser():
parser = argparse.ArgumentParser(description='instant coding answers via the command line')
parser.add_argument('query', metavar='QUERY', type=str, nargs='*',
help='the question to answer')
parser.add_argument('-p', '--pos', help='select answer in specified position (default: 1)', default=1, type=int)
parser.add_argument('-a', '--all', help='display the full text of the answer',
action='store_true')
parser.add_argument('-l', '--link', help='display only the answer link',
action='store_true')
parser.add_argument('-c', '--color', help='enable colorized output',
action='store_true')
parser.add_argument('-n', '--num-answers', help='number of answers to return', default=1, type=int)
parser.add_argument('-C', '--clear-cache', help='clear the cache',
action='store_true')
parser.add_argument('-v', '--version', help='displays the current version of howdoi',
action='store_true')
return parser #启动函数
def command_line_runner():
parser = get_parser()
args = vars(parser.parse_args()) # 输出脚本版本
if args['version']:
print(__version__)
return
# 清除缓存
if args['clear_cache']:
_clear_cache()
print('Cache cleared successfully')
return
# 如果没有query,就输出帮助信息
if not args['query']:
parser.print_help()
return # 如果环境变量设置了禁止缓存,就清除缓存
if not os.getenv('HOWDOI_DISABLE_CACHE'):
_enable_cache()
# 彩色输出
if os.getenv('HOWDOI_COLORIZE'):
args['color'] = True
# 如果用户Python版本小于3就进行utf-8编码,如否,就正常启动
if sys.version < '3':
print(howdoi(args).encode('utf-8', 'ignore'))
else:
print(howdoi(args)) if __name__ == '__main__':
command_line_runner()

howdoi 简单分析的更多相关文章

  1. 简单分析JavaScript中的面向对象

    初学JavaScript的时候有人会认为JavaScript不是一门面向对象的语言,因为JS是没有类的概念的,但是这并不代表JavaScript没有对象的存在,而且JavaScript也提供了其它的方 ...

  2. CSipSimple 简单分析

    简介 CSipSimple是一款可以在android手机上使用的支持sip的网络电话软件,可以在上面设置使用callda网络电话.连接使用方式最好是使用wifi,或者3g这样上网速度快,打起电话来效果 ...

  3. C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析与解决方法

    对于C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析,目前本人分析两种情况,如下: 情况一: 借鉴麒麟.NET ...

  4. 透过byte数组简单分析Java序列化、Kryo、ProtoBuf序列化

    序列化在高性能网络编程.分布式系统开发中是举足轻重的之前有用过Java序列化.ProtocolBuffer等,在这篇文章这里中简单分析序列化后的byte数组观察各种序列化的差异与性能,这里主要分析Ja ...

  5. 简单分析Java的HashMap.entrySet()的实现

    关于Java的HashMap.entrySet(),文档是这样描述的:这个方法返回一个Set,这个Set是HashMap的视图,对Map的操作会在Set上反映出来,反过来也是.原文是 Returns ...

  6. Ffmpeg解析media容器过程/ ffmpeg 源代码简单分析 : av_read_frame()

    ffmpeg 源代码简单分析 : av_read_frame() http://blog.csdn.net/leixiaohua1020/article/details/12678577 ffmpeg ...

  7. FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  8. FFmpeg资料来源简单分析:libswscale的sws_getContext()

    ===================================================== FFmpeg库函数的源代码的分析文章: [骨架] FFmpeg源码结构图 - 解码 FFmp ...

  9. wp7之换肤原理简单分析

    wp7之换肤原理简单分析 纠结很久...感觉勉强过得去啦.还望各位大牛指点江山 百度找到这篇参考文章http://www.cnblogs.com/sonyye/archive/2012/03/12/2 ...

随机推荐

  1. CSDN极客头条使用指南

    CSDN极客头条使用指南 今天给大家介绍一下CSDN博客最新推出的这个栏目--CSDN极客头条. 极客头条是什么 极客头条大家分享优质IT资源的聚集地. 大家不仅能够分享CSDN的文章,更能够将其它社 ...

  2. vue的iview列表table render函数设置DOM属性值的方法

    { title: '负责人社保照片', key: 'leaderIdNumber', render: (h, params) => { return h('img',{domProps:{ sr ...

  3. struts2危险漏洞解决方法

    原创,bgy编写.2013-07-24 前文: 随着苹果开发者网站的沦陷,已经曝光一周的Apache Struts2漏洞再次成为热门话题,今天有消息称由于该漏洞被利用,淘宝的数据库已经被盗,尽管淘宝官 ...

  4. JavaBridge

    有的时候我们需要在PHP里调用JAVA平台封装好的jar包里的class类和方法 一般的做法是采用php-java-bridge做桥接 1.实现原理: 先打开java的一个监听端口,php调用java ...

  5. python的redis简单使用

    安装的Python版本 3.6.1 redis安装的2.8 安装redis模块 pip install redis 简单使用 redis-test.py import redis r=redis.Re ...

  6. C# DataTable转实体 通用方法

    public static T GetEntity<T>(DataTable table) where T : new() { T entity = new T(); foreach (D ...

  7. 禁止requests请求https的提示InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more

     提示这个 InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from ...

  8. TCP拥塞控制算法纵横谈-Illinois和YeAH

    周五晚上.终于下了雨.所以也终于能够乱七八糟多写点松散的东西了... 方法论问题. 这个题目太大以至于内容和题目的关联看起来有失偏颇.只是也无所谓,既然被人以为"没有方法论"而歧视 ...

  9. javascript 作用域、作用域链理解

    JavaScript作用域就是变量和函数的可访问范围. 1.变量作用域 在JavaScript中,变量作用域分为全局作用域和局部作用域. 全局作用域 任何地方都可以定义拥有全局作用域的变量 1.没有用 ...

  10. weblogic上部署项目出错

    一. Unable to access the selected application. Exception in AppMerge flows' progression Exception in ...