本文
http://afra55.github.io/2018/01/22/python-tenth-step/
推荐用 http://cn.python-requests.org/zh_CN/latest/
Web 客户端和服务器
HTTP( HyperText Transfer Protocol, 超 文本 传输)
HTTP 是 TCP/ IP 的 上层 协议, 这 意味着 HTTP 协议 依靠 TCP/ IP 来 进行 低层 的 交流 工作
SSL (Secure SocketLayer) 安全套接字层, 用来创建一个套接字,加密通过该套接字传传输的数据
URL (Uniform Resource Identifier) 统一资源标识符, scheme://netloc[:port]/path/[;params][?query]#fragment
urllib 模块
urllib 提供 了 一个 高级的 Web 通信 库, 支持 基本 的 Web 协议, 如 HTTP、 FTP 和 Gopher 协议, 同时 也 支持 对本 地 文件 的 访问
urllib.parse 模块
urlparse(url, scheme='', allow_fragments=True)
将 url 解析为6元组,(scheme, netloc, path, params, query, fragment)
values = urlparse("http://so.csdn.net/so/search/s.do?q=AAA啊哈&t=blog")
print(values) # ParseResult(scheme='http', netloc='so.csdn.net', path='/so/search/s.do', params='', query='q=AAA啊哈&t=blog', fragment='')
print(urlunparse(values)) # http://so.csdn.net/so/search/s.do?q=AAA啊哈&t=blog
urlunparse() 将 urlparse 处理生成的6元组拼接成 url 并返回
urljoin(base, url, allow_fragments=True)
print(urljoin('https://docs.python.org/3/faq/index.html', 'general.html#what-is-python'))
# https://docs.python.org/3/faq/general.html#what-is-python
urljoin() 将 baseurl 的根域名和 newurl 拼合成一个完整的 URL
urllib.parse 函数 | 描述 |
---|---|
quote(string, safe=’/’) | 对 string 在 URL 无法使用的字符进行编码, safe 中的字符不需要编码 |
quote_plus(string, safe=’/’) | 与 quote() 类似,除了将空格编码为 ‘+’ 号 |
unquote(string) | 和 quote() 功能相反 |
unquote_plus(string) | 和 quote_plus() 功能相反 |
urlencode(dict) | 将字典的 key 和 value 进行quote_plus() 转换为有效的 CGI 查询字符串,并变为 ‘键=值’ 的格式 |
quote(string, safe='/', encoding=None, errors=None)
获取 url 数据,并将其编码使其可以用于 url 字符串中,那些 URL 不能 使用 的 字符 前边 会被 加上 百分 号(%) 同时 转换 成 十六进制, 例如,“% xx”, 其中,“ xx” 表示 这个 字母 的 ASCII 码 的 十六进制 值, 逗号、 下划线、 句号、 斜线 和 字母 数字 这类 符号 不需要 转化, 其 他的 则 均需 要 转换
quote('abc def') # 'abc%20def'
quote_plus(string, safe='', encoding=None, errors=None)
与quote() 类似,但是还可以将空格编码为 ‘+’号
quote_plus('abc def') # 'abc+def'
unquote(string, encoding='utf-8', errors='replace')
和 quote() 功能相反, 将 所有 编码 为“% xx” 式 的 字符 转换 成 等价 的 ASCII 码 值
unquote_plus(string, encoding='utf-8', errors='replace')
与 unquote() 类似,除此之外会将 ‘+’ 转换为 空格
urlencode(query, doseq=False, safe='', encoding=None, errors=None, quote_via=quote_plus)
将字典的 key 和 value 进行quote_plus() 转换为有效的 CGI 查询字符串,并变为 ‘键=值’ 的格式
urllib.request 模块
urllib.request 函数 | 描述 |
---|---|
urlopen(url, data=None) | 打开 url,如果是 POST 请求,则通过 data 发送请求数据 |
urlretrieve(url, filename=None, reporthook=None) | 将 url 中的文件下载到 filename 或临时文件中, reporthook 函数会获取到统计信息 |
urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *, cafile=None, capath=None, cadefault=False, context=None)
urlopen(), 打开给定 url 字符串表示的 web 连接,并返回文件类型对象。如果没有给定协议或者 scheme 或者传入文件时打开一个本地文件
urlopen() 对象方法 | 描述 |
---|---|
f.read([bytes] ) |
从 f 中读出所有子节,或 bytes 个字节 |
f.readline() | 从 f 中读取一行 |
f.readlines() | 以列表形式返回 f 中的所有行 |
f.close() | 关闭 f 的 url 连接 |
f.fileno() | 返回 f 的文件句柄 |
f.info() | 获得 f 的 MIME 头文件 |
f.geturl() | 返回 f 的真正url |
urlretrieve(url, filename=None, reporthook=None, data=None)
urlretrieve(), 下载 url 打开的完整的 html 到本地硬盘。其返回一个二元组(filename, mime_hdrs),filename 是含有下载数据的本地文件名,mime_hdrs 是 Web 服务器响应后返回的一系列 MIME 文件头。如果提供 reporthook(blocknum, bs, size) 函数,则在每块数据下载或传输完成后调用这个函数来提供 块编号、读取的块大小、块总大小
urllib2 模块
urllib2 可以 处理 更 复杂 URL 的 打开 问题。 例如 有 基本 验证( 登录 名 和 密码) 需求 的 Web 站点。 通过 验证 的 最简单 方法 是 使用 前边 章节 描述 的 URL 中的 net_ loc 组件
HTTP 验证示例
import urllib.request, urllib.error, urllib.parse
LOGIN = 'wesley'
PASSWD = "you'llNeverGuess"
URL = 'http://localhost'
REALM = 'Secure Archive'
def handler_version(url):
hdlr = urllib.request.HTTPBasicAuthHandler()
hdlr.add_password(REALM, urllib.parse.urlparse(url)[1], LOGIN, PASSWD)
opener = urllib.request.build_opener(hdlr)
urllib.request.install_opener(opener)
return url
def request_version(url):
from base64 import encodestring
req = urllib.request.Request(url) # 创建一个 request 对象
b64str = encodestring(bytes('%s:%s' % (LOGIN, PASSWD), 'utf-8'))[:-1] # 验证头信息
req.add_header("Authorization", "Basic %s" % b64str)
return req
for funcType in ('handler', 'request'):
print('*** Using %s:' % funcType.upper())
url = eval('%s_version' % funcType)(URL)
f = urllib.request.urlopen(url)
print(str(f.readline(), 'utf-8'))
f.close()
推荐使用 requests
爬虫
使用 requests
一个爬小说的例子:
import requests
from bs4 import BeautifulSoup
server = 'http://www.biqukan.com'
target = 'http://www.biqukan.com/1_1496/'
def req_get_bf(url):
return BeautifulSoup(requests.get(url).text, 'html.parser')
def get_content(content_url):
bf = req_get_bf(content_url)
texts = bf.find_all('div', 'showtxt')
return texts[0].text.replace('\xa0' * 8, '\n\n')
def get_target_url(main_url, name_list, url_list):
div_bf = req_get_bf(main_url)
div = div_bf.find_all('div', 'listmain')
a_div_bf = BeautifulSoup(str(div[0]), 'html.parser')
a = a_div_bf.find_all('a')[12:]
for item in a:
url_list.append(server + item.get('href'))
name_list.append(item.string)
return len(a)
def write_to_file(name, path, text):
with open(path, 'a', encoding='utf-8') as f:
f.write(name + '\n')
f.writelines(text)
f.write('\n\n')
if __name__ == '__main__':
names = [] # 存放章节名
urls = [] # 存放章节链接
nums = get_target_url(target, names, urls)
for i in range(nums):
print('正在下载', names[i])
write_to_file(names[i], '龙王传说.txt', get_content(urls[i]))
Web(http)服务器
要 建立 一个 Web 服务器, 必须 建立 一个 基本 的 服务器 和 一个“ 处理 程序”
基础 的 Web 服务器 是一 个 模板。 其 角色 是在 客户 端 和 服务器 端 完成 必要 的 HTTP 交互
处理 程序 是一 些 处理 主要“ Web 服务” 的 简单 软件。 它 用于 处理 客户 端 的 请求, 并 返回 适当 的 文件, 包括 静态 文件 或 动态 文件。 处理 程序 的 复杂性 决定了 Web 服务器 的 复杂 程度
以下是三种不同的处理程序,都整合在 http.server 模块中:
- BaseHTTPResquestHandler 除了 获得 客户 端 的 请求 外, 没有 实现 其他 处理 工作
- SimpleHTTPRequestHandler 建立 在 BaseHTTPResquestHandler 的 基础上, 以 非常 直接 的 形式 实现 了 标准 的 GET 和 HEAD 请求
- CGIHTTPRequestHandler 这个 处理 程序 可以 获取 SimpleHTTPRequestHandler, 并 添 加了 对 POST 请求 的 支持,其 可以 调用 CGI 脚本 完成 请求 处理 过程, 也可以 将 生成 的 HTML 脚本 返回 给 客户 端
一个简单的 Web服务器示例,可以读取 GET 请求,获取 Web页面 html文件,并返回给调用的客户端:
from http.server import BaseHTTPRequestHandler, HTTPServer
class MyHandler(BaseHTTPRequestHandler):
def do_get(self):
try:
f = open(self.path[1:], 'r')
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(f.read())
f.close()
except IOError:
self.send_error(404, 'File Not Found: %s' % self.path)
def main():
server = HTTPServer(('', 8080), MyHandler)
try:
print('Welcome to the machine...')
print('Press ^C once or twice to quit')
server.serve_forever()
except KeyboardInterrupt:
print('^C received, shutting down server')
finally:
if server:
server.socket.close()
if __name__ == '__main__':
main()
Web 应用程序 | 描述 |
---|---|
cgi | 冲标准网关(CGI)获取数据 |
cgitb | 处理 CGI 返回数据 |
HTMLparse | HTML, XHTML 解析器 |
htmlentitydefs | 一些 HTML 普通实体定义 |
Cookie | 用于 HTTP 状态管理的服务器端 cookie |
cookielib | HTTP 客户端的 cookie 处理类 |
webbrowser | 控制器:向浏览器加载文档 |
sgmllib | 解析简单的 SGML 文件 |
robotparser | 解析 robots.txt 文件,对 URL 做“可获得性分析” |
httplib | 用来创建 HTTP 客户端 |
urllib | 通过 URL 或相关工具访问服务器 |
Xml 处理 | 描述 |
---|---|
xml | 包含许多不同解析器的 XML 包 |
xml.sax | 用于兼容 SAX2 的 XML(SAX)解析器 |
xml.dom | 文档对象模型(DOM)XML解析器 |
xml.etree | 树型 xml 解析器,基于 Element 灵活容器对象 |
xml.parsers.expat | 非验证型 Expat XML 解析器的接口 |
xmlrpclib | 通过 HTTP 提供 XML 远程过程调用 (RPC)客户端 |
SimpleXMLRPCServer | python XML-RPC 服务器的基本框架 |
DocXMLRPCServer | 自描述XML-RPC 服务器的基本框架 |
Web 服务器 | 描述 |
---|---|
BaseHTTPServer | 开发 Web服务器的抽象类 |
SimpleHTTPServer | 处理最简单的 HTTP 请求 (HEAD 和 GET) |
CGIHTTPServer | 像 SimpleHTTPServer 一样处理 Web文件,还能处理 CGI (HTTP POST) 请求 |
wsgiref | 定义 Web 服务器和 Python Web 应用程序间的标准接口的包 |
Web 编程:CGI 和 WSGI
CGI, 通用网关接口
WSGI,Web服务器网关接口
创建 Web服务
创建文件 cgihttpd.py
, 包含如下内容:
from http.server import CGIHTTPRequestHandler, test
test(CGIHTTPRequestHandler) # test 默认 port 为 8000
然后使用命名行执行: python3 cgihttpd.py
在 cgihttpd.py
相同目录下创建文件夹 cgi-bin
, 放入 Python CGI 脚本
将 一些 HTML 文件 放到 启动 服务器 的 目录 中, 可能 要在 cgi-bin
中 放 些 Python CGI 脚本, 然后 就可 以在 地址 栏中 输入 这些 地址 来 访问 Web 站点
http://localhost:8000/hello.htm
http://localhost:8080/cgi-bin/helloA.py
可以通过以下方式在 cgi 脚本中获取表单:
form = cgi.FieldStorage() # 获取 表单
if 'person' in form:
who = form['person'].value # 获取表单中 key person 的值
else:
who = 'NEW USER'
print(who) # 通过 print 返回数据
以下是使用 UTF-8 进行编码输出的示例:
print('''Content-Type: text/html; charset=UTF-8
真实数据
'''.replace('\n', '\r\n'))
目前, CGI 特别 指出 只允许两种表单编码:“application/x-www-form-urlencoded”和“multipart/form-dat”, 前者是默认的
cookie 通俗点来说是 Web 站点 服务器 要求 保存 在 客户 端( 如 浏览器) 上 的 二进制 数据
cookie 是以 分号 分隔 的 键值 对 存在 的, 即 以 分号() 分隔 各个 键值 对, 每个 键值 对 中间 都由 等号(=) 分开
一旦 在 客户 端 建立 了 cookie, HTTP_COOKIE 环境 变量 会 将那 些 cookie 自动 放到 请求 中 发送 给 服务器
from os import environ
if 'HTTP_COOKIE' in environ: # 获取 cookie
cookies = [x.strip() for x in environ['HTTP_COOKIE'].split(';')]
设置 cookie 就是服务器向客户端发送 Set-Cookie
头文件要求客户端存储 cookie
print('Set-Cookie: cookie_key=cookie_value; path=/')
WSGi
WSGI 不是 服务器, 也不 是 用于 与 程序 交互 的 API, 更不 是 真实的 代码, 而 只是 定义 的 一个 接口
其 目标 是在 Web 服务器 和 Web 框架 层 之间 提供 一个 通用 的 API 标准, 减少 之 间的 互 操作性 并 形成 统一 的 调用 方式
def simple_wsgi_app(environ, start_response):
status = '200 OK'
headers = [('Content-type', 'text/plain')]
start_response(status, headers)
return ['Hello world!']
environ 变量 包含 一些 熟悉 的 环境 变量, 如 HTTP_ HOST、 HTTP_ USER_ AGENT、 SERVER_ PROTOCOL 等。 而 start_ response() 这个 可调 用 对象 必须 在 应用 执行, 生成 最终 会 发送 回 客户 端 的 响应。 响应 必须 含有 HTTP 返回 码( 200、 300 等), 以及 HTTP 响应 头
详细,可以参考示例 simple_server.py
:
"""BaseHTTPServer that implements the Python WSGI protocol (PEP 3333)
This is both an example of how WSGI can be implemented, and a basis for running
simple web applications on a local machine, such as might be done when testing
or debugging an application. It has not been reviewed for security issues,
however, and we strongly recommend that you use a "real" web server for
production use.
For example usage, see the 'if __name__=="__main__"' block at the end of the
module. See also the BaseHTTPServer module docs for other API information.
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import sys
import urllib.parse
from wsgiref.handlers import SimpleHandler
from platform import python_implementation
__version__ = "0.2"
__all__ = ['WSGIServer', 'WSGIRequestHandler', 'demo_app', 'make_server']
server_version = "WSGIServer/" + __version__
sys_version = python_implementation() + "/" + sys.version.split()[0]
software_version = server_version + ' ' + sys_version
class ServerHandler(SimpleHandler):
server_software = software_version
def close(self):
try:
self.request_handler.log_request(
self.status.split(' ',1)[0], self.bytes_sent
)
finally:
SimpleHandler.close(self)
class WSGIServer(HTTPServer):
"""BaseHTTPServer that implements the Python WSGI protocol"""
application = None
def server_bind(self):
"""Override server_bind to store the server name."""
HTTPServer.server_bind(self)
self.setup_environ()
def setup_environ(self):
# Set up base environment
env = self.base_environ = {}
env['SERVER_NAME'] = self.server_name
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['SERVER_PORT'] = str(self.server_port)
env['REMOTE_HOST']=''
env['CONTENT_LENGTH']=''
env['SCRIPT_NAME'] = ''
def get_app(self):
return self.application
def set_app(self,application):
self.application = application
class WSGIRequestHandler(BaseHTTPRequestHandler):
server_version = "WSGIServer/" + __version__
def get_environ(self):
env = self.server.base_environ.copy()
env['SERVER_PROTOCOL'] = self.request_version
env['SERVER_SOFTWARE'] = self.server_version
env['REQUEST_METHOD'] = self.command
if '?' in self.path:
path,query = self.path.split('?',1)
else:
path,query = self.path,''
env['PATH_INFO'] = urllib.parse.unquote(path, 'iso-8859-1')
env['QUERY_STRING'] = query
host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0]
if self.headers.get('content-type') is None:
env['CONTENT_TYPE'] = self.headers.get_content_type()
else:
env['CONTENT_TYPE'] = self.headers['content-type']
length = self.headers.get('content-length')
if length:
env['CONTENT_LENGTH'] = length
for k, v in self.headers.items():
k=k.replace('-','_').upper(); v=v.strip()
if k in env:
continue # skip content length, type,etc.
if 'HTTP_'+k in env:
env['HTTP_'+k] += ','+v # comma-separate multiple headers
else:
env['HTTP_'+k] = v
return env
def get_stderr(self):
return sys.stderr
def handle(self):
"""Handle a single HTTP request"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
def demo_app(environ,start_response):
from io import StringIO
stdout = StringIO()
print("Hello world!", file=stdout)
print(file=stdout)
h = sorted(environ.items())
for k,v in h:
print(k,'=',repr(v), file=stdout)
start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
return [stdout.getvalue().encode("utf-8")]
def make_server(
host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
server.set_app(app)
return server
if __name__ == '__main__':
with make_server('', 8000, demo_app) as httpd:
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc')
httpd.handle_request() # serve one request, then exit