知乎:https://www.zhihu.com/people/monkey.d.luffy Android高级开发交流群2: 752871516
全部博文(315)
分类: Python/Ruby
2012-02-22 21:44:20
网络(web)
这章主要是web的应用了;很容易想到网页,不过里面有更加好的东西,比如网络爬虫(aka蜘蛛,机器人),挺有意思,估计很深奥?
开始练习了一个程序,就是urllib2模块的使用,利用request方式和handler方式去获取网页信息,里面加了登录密码认证,具体内部的机制,不懂?(由于上不了网,我就用以前C写的web程序,就显示了文字和图片的网页来练习的)
一》》练习代码:urlopenAuth.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import urllib2
LOGIN = 'hl'
PASSWD = "you'llNeverGuess"
URL = '' #学习c的时候写的web服务器下的主页
#---以下是关于urllib2模块的复杂打开问题;涉及到认证的概念,具体不是很懂,用法??
def handler_version(url):
from urlparse import urlparse as up
hdlr = urllib2.HTTPBasicAuthHandler() #创建一个认证处理器
hdlr.add_password('hl', up(url)[1], LOGIN, PASSWD) #添加用户名和密码,up负责解析url地址并传入第“1”个参数netloc--127.0.0.1:8000
#print up(url) #解析后:ParseResult(scheme='http', netloc='127.0.0.1:8000', path='/www/index.html', params='', query='', fragment='')
opener = urllib2.build_opener(hdlr) #建立一个url打开器,这个打开器具有安全性
urllib2.install_opener(opener) #安装这个打开器
return url
def request_version(url): #这是利用request方式来读取html文件
from base64 import encodestring
req = urllib2.Request(url) #创建一个请求对象
b64str = encodestring('%s:%s' % (LOGIN, PASSWD))[:-1] #编码登录密码,作为参数添加到request对象中,作为认证处理使用;
req.add_header("Authorization", "Basic %s" % b64str)
return req
if __name__ == "__main__":
for funcType in ('handler', 'request'):
print '*** Using %s:' % funcType.upper() #大写转换
#url = eval('%s_version')(URL) #这样按着书本的不对,漏写函数名了,后面把函数名称先做了拼接;
url = eval('%s_version' % funcType)(URL) #利用eval来执行函数,参数为URL
f = urllib2.urlopen(url) #打开url,读取内容
print f.readline()
f.close()
结果:
huanglei@ubuntu:~/python/web$ python urlopenAuth.py
*** Using HANDLER:
*** Using REQUEST:
二》》网络爬虫事例(这个部分主要是了解作者的思路,而自己还不怎么能写出这样的列子,所以就当作是学习下整体框架,以后助于自己的事业!)craw.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from sys import argv
from os import makedirs, unlink, sep
from os.path import dirname, exists, isdir, splitext
from string import replace, find, lower
from htmllib import HTMLParser
from urllib import urlretrieve
from urlparse import urlparse, urljoin
from formatter import DumbWriter, AbstractFormatter #?
from cStringIO import StringIO
class Retriever(object):
def __init__(self, url):
self.url = url
self.file = self.filename(url) #这个地方调用filename函数,获取文件路径
print '---self.file---', self.file #127.0.0.1:8000/friends.html
def filename(self, url, deffile='index.html'):
parsedurl = urlparse(url, 'http:', 0)
print '---parsedurl----', parsedurl #ParseResult(scheme='http', netloc='127.0.0.1:8000', path='/friends.html', params='', query='', fragment='')
path = parsedurl[1] + parsedurl[2]
print '---path----', path #127.0.0.1:8000/friends.html
ext = splitext(path)
print '---ext----', ext #('127.0.0.1:8000/friends', '.html')
if ext[1] == '':
if path[-1] == '/':
path += deffile
else:
path += '/' + deffile
print '---path2----', path #127.0.0.1:8000/friends.html
ldir = dirname(path)
print '---ldir-----', ldir #127.0.0.1:8000
print '---sep-----', sep # sep == '/'
if sep != '/': #在windows下和linux下的分割形式不同;
ldir = replace(ldir, '/', sep)
if not isdir(ldir):
if exists(ldir):
unlink(ldir)
makedirs(ldir)
return path
def download(self):
try:
retval = urlretrieve(self.url, self.file)
print '---retval---', retval #('127.0.0.1:8000/friends.html',
except IOError:
retval = ('***ERROR: invalid URL "%s"' % self.url, )
return retval
def parseAndGetLinks(self):
self.parser = HTMLParser(AbstractFormatter(DumbWriter(StringIO())))
#
self.parser.feed(open(self.file).read()) #读取网页文件,做解析;网页链接会自动增加到一个列表里面anchorlist[]
self.parser.close()
print '---self.parser---%s\n---self.parser.anchorlist---%s' % (self.parser, self.parser.anchorlist)
return self.parser.anchorlist #[] #网页无链接网页,所以为空列表
class Crawler(object):
count = 0
def __init__(self, url):
self.q = [url] #等待下载链接的队列
self.seen = [] #已经看过的链接列表
self.dom = urlparse(url)[1] #用作判断是否属于同一个链接域
print '---self.dom---', self.dom #127.0.0.1:8000
def getPage(self, url):
r = Retriever(url) #类实例
print '----r----', r #<__main__.Retriever object at 0x891810c>
retval = r.download()
print '---retval---', retval
if retval[0] == '*':
print retval, '...skipping parse'
return
Crawler.count += 1
print '\n(', Crawler.count, ')'
print 'URL:', url
print 'FILE:', retval[0]
self.seen.append(url) #每看过一个网页,我们就把它加入到浏览列表里面
links = r.parseAndGetLinks() #获取网页链接
for eachLink in links: #对于链接做各种处理
if eachLink[:4] != 'http' and find(eachLink, '://') == -1:
eachLink = urljoin(url, eachLink)
print '*', eachLink
if find(lower(eachLink), 'mailto:') != -1:
print '...discarded, mailto link'
continue
if eachLink not in self.seen:
if find(eachLink, self.dom) == -1:
print '...discarded, not in domain'
else:
if eachLink not in self.q:
self.q.append(eachLink) #把链接也加入到网页地址列表中;
print '... new, added to Q'
else:
print '... discarded, already in Q'
else:
print '...discarded, already processed'
def go(self):
while self.q:
url = self.q.pop() #出队列,为空则停止;
self.getPage(url) #核心抓取网页的函数
def main():
if len(argv) > 1: #参数的简单处理,如果你输入地址作为命令行参数,则直接读取;否则运行时输入;
url = argv[1]
else:
try:
url = raw_input('Enter starting URL: ')
except (KeyboardInterrupt, EOFError):
url = ''
if not url:
return
robot = Crawler(url)
robot.go()
if __name__ == "__main__":
main() #自己需要从主函数开始流走整个过程…
Result: 以下打印信息有助于了解整个过程都做了什么???同时在当前服务器目录下建立了名为127.0.0.1:8000的目录用于存放网页内容,这里存放的是127.0.0.1:8000/friends.html文件
huanglei@ubuntu:~/python/web$ python crawl.py
Enter starting URL:
---self.dom--- 127.0.0.1:8000
---parsedurl---- ParseResult(scheme='http', netloc='127.0.0.1:8000', path='/friends.html', params='', query='', fragment='')
---path---- 127.0.0.1:8000/friends.html
---ext---- ('127.0.0.1:8000/friends', '.html')
---path2---- 127.0.0.1:8000/friends.html
---ldir----- 127.0.0.1:8000
---sep----- /
---self.file--- 127.0.0.1:8000/friends.html
----r---- <__main__.Retriever object at 0x954e10c>
---retval---
('127.0.0.1:8000/friends.html',
---retval---
('127.0.0.1:8000/friends.html',
( 1 )
URL:
FILE: 127.0.0.1:8000/friends.html
---self.parser---
---self.parser.anchorlist---[]
我在网页中加入一个返回主页的链接:
结果就会多一个了:
Enter starting URL:
---self.dom--- 127.0.0.1:8000
---parsedurl---- ParseResult(scheme='http', netloc='127.0.0.1:8000', path='/friends.html', params='', query='', fragment='')
---path---- 127.0.0.1:8000/friends.html
---ext---- ('127.0.0.1:8000/friends', '.html')
---path2---- 127.0.0.1:8000/friends.html
---ldir----- 127.0.0.1:8000
---sep----- /
---self.file--- 127.0.0.1:8000/friends.html
----r---- <__main__.Retriever object at 0x939f12c>
---retval--- ('127.0.0.1:8000/friends.html',
---retval---
('127.0.0.1:8000/friends.html',
( 1 )
URL:
FILE: 127.0.0.1:8000/friends.html
---self.parser---
---self.parser.anchorlist---['']
*
... new, added to Q
---parsedurl---- ParseResult(scheme='http', netloc='127.0.0.1:8000', path='/index.html', params='', query='', fragment='')
---path---- 127.0.0.1:8000/index.html
---ext---- ('127.0.0.1:8000/index', '.html')
---path2---- 127.0.0.1:8000/index.html
---ldir----- 127.0.0.1:8000
---sep----- /
---self.file--- 127.0.0.1:8000/index.html
----r---- <__main__.Retriever object at 0x9404bac>
---retval--- ('127.0.0.1:8000/index.html',
---retval--- ('127.0.0.1:8000/index.html',
( 2 )
URL:
FILE: 127.0.0.1:8000/index.html
---self.parser---
---self.parser.anchorlist---[]
三》》CGI(感触颇深,在做广告机项目的时候,就涉及到CGI,要是会的话,就可以很快帮助组员把项目完善了,搞得最后就有媒体部分,没有网页控制部分!)
了解Cgi,我们需要看他如何运行起来,可以按照apach服务器来做。但是上不了网,而且作者建议利用python提供的简单的web服务器来处理Cgi程序;
步骤1:(可以在任意目录运行这个服务器)
huanglei@ubuntu:~/python/web$ python -m CGIHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
步骤2:(在相应的目录下创建一个Cgi-bin目录---这不是必须的,只有指定你访问的文件的路径就好了;然后放上你的html网页,或者py文件到Cgi-bin中去等等)
huanglei@ubuntu:~/python/web$ mkdir Cgi-bin
huanglei@ubuntu:~/python/web$ cp /home/huanglei/webserver2.0/www/index.html ./ #以前做的那个网页,和刚开始文章展示的一样
huanglei@ubuntu:~/python/web$ cp -r /home/akaedu/webserver2.0/www/images/ ./ #不要忘了相应的图片目录
步骤3:可以访问你的网页了(利用启动的web服务器)
网页地址栏输入: 没有在Cgi-bin目录就直接
运行时,web服务器显示:
localhost - - [05/Feb/2012 02:39:14] "GET /index.html HTTP/1.1" 200 -
localhost - - [05/Feb/2012 02:39:14] "GET /images/642419907.jpg HTTP/1.1" 200 -
运行时, 网页内容为代码内容(可以加以处理),服务器显示:
localhost - - [05/Feb/2012 02:41:42] "GET /Cgi-bin/urlopenAuth.py HTTP/1.1" 200 -
到这里我已经觉得没有白学,看到可以跑起网页,渐渐的Cgi也会很熟悉,广告机能完全搞定,至少毕业设计不用担心了!学习,就是多学就好…其实很重要的就是遇到问题能够解决,并且能纠正一些错误,书本的,自己犯的,都是有意义的,如果程序员真的变成了一个编程机器,我觉得这个公司也没有什么前途了,至少我希望大家不要这样,我的话就只有努力才能赶上别人了!!!
继续:书本带领我开始从表单到Cgi处理程序,首先就是一个表单网页friends.html---我添加了单选按钮提示,内容嘛:(里面有处理动作/Cgi-bin/friends1.py程序,暂时空着,因为我们要运行这个网页,同时查看服务器和网页提交后的信息,都是对你今后了解整个过程有用的哦!)
Friends CGI Demo (static screen)
Friends
list for: NEW USER
Enter your Name:
How many friends do you have?
0
10
25
50
100
运行>> (没有处理脚本前)
huanglei@ubuntu:~/python/web$ python -m CGIHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
(火狐)地址栏输入:
显示:
提交:
服务器显示:localhost - - [05/Feb/2012 03:04:56] "GET /cgi-bin/friends1.py?person=13&howmany=0 HTTP/1.1" 404 –
之后网页地址栏变为: 类似一种网页格式,附带键值对…
这样以后我们再来看CGI相关概念,应该会清晰很多了:
FROM: CGI 应用程序和典型的应用程序有些不同。主要的区别在于输入、输出以及用户和计算机交互方面。当一个 CGI 脚本开始执行时,它需要检索用户-支持表单,但这些数据必须要从 Web 的客户端才可以获得,而不是从服务器或者硬盘上获得。这些不同于标准输出的输出将会返回到连接的 Web 客户端,而不是返回到屏幕、CUI 窗口或者硬盘上。这些返回来的数据必须是具有一系列有效头文件的 HTML。否则,如果浏览器是 Web 的客户端,由于浏览器只能识别有效的 HTTP 数据(也就是 MIME 都问价和 HTML),那么返回的也只能是个错误消息(具体的就是因特网服务器错误)。最后,可能和你想象的一样,用户不能与脚本进行交互。所有的交互都将发生在 Web 客户端(用户的行为),Web 服务器端和 CGI 应用程序间。
FROM: 在 cgi 模块中有个主要类:FieldStorage 类,它完成了所有的工作。在 Python CGI 脚本开始时这个类将会被实例化,它会从 Web 客户端(具有 Web 服务器)读出有关的用户信息。一旦这个对象被实例化,它将会包含一个类似字典的对象,具有一系列的键-值对,键就是通过表单传入的表单条目的名字,而值则包含相应的数据。这些值本身可以是以下三种对象之一。它们既可以是 FieldStorage 对象(实例)也可以是另一个类似的名为 MiniFieldStorage 类的实例,后者用在没有文件上传或 mulitple-part 格式数据的情况。MiniFieldStorage 实例只包含名字和数据的键-值对。最后,它们还可以是这些对象的列表。这发生在表单中的某个域有多个输入值的情况下。
继续;我们附上friends1.py程序来处理获取的数据,同时显示新的网页内容,不过这里是动态的---原因是因为我们的“新网页”是由CGI产生的,而不是一个静态的事先做好的网页:Cgi-bin/friends1.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import cgi
reshtml = '''Context-Type: text/html\n\n
Friends CGI Demo (dynamic screen)
Friends list for:
%s
Your name is: %s
You have %s friends.
'''
if __name__ == "__main__":
form = cgi.FieldStorage()
who = form['person'].value
howmany = form['howmany'].value
print reshtml % (form, who, howmany)
问题:在重新打开网页,并且提交后,结果页面只是代码?
解决:我晚上就在那琢磨,用手机上网查各种信息,今天早上看了别人写的例子,修改了下那个Cgi-bin目录为小写的cgi-bin,我操,一试果然可以了。晕死我了,原来说是python的web服务器在解释执行cgi程序的时候,只识别cgi-bin目录下的.py文件或者.sh等可执行文件。这样结果就行了哈,不然咋进行下一步啊:(至于后面比较复杂的例子我就不练习了,我认为只要教你入门,就要自己发散思维去实践,扩展,不会网上查查,不然都练习去了,没有自己的思维,学到的东西就很少了)
四》》Cookie(waiting…)--- 服务器可以向客户端发送一个请求来保存 cookie,而不必用在返回的 Web 页面中嵌入数据的方法来保持数据。想想我们在cgi程序中获取表单信息,然后再以网页的形式显示获取的结果;如果我们大量提交“表单”,那么每次都会运行这个处理程序,无疑这会消化很多内存,如果cpu释放资源过慢,或者你的程序就不去释放,那么日积月累,你的服务器迟早“崩溃”,所以我们需要采用一种类似缓存的方式来保存我们常用的数据,或者最新的数据,这样可以大大减少网络资源的消耗!
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import cgi
import Cookie
reshtml = '''Content-Type: text/html\n
Friends CGI Demo (dynamic screen)
Friends list for:
%s
Your name is: %s
You have %s friends.
Get cookie %s
'''
cookie = Cookie.SimpleCookie() #简单定义了一个cookie,用来保存数据和再网页上显示它;高级的还要学习…
if __name__ == "__main__":
form = cgi.FieldStorage()
who = form['person'].value
howmany = form['howmany'].value
cookie.load("ys-tab = entrence;person=%s;howmany=%s" % (who, howmany)) #ys-tab = xxx这个不会装载到cookie中;我认为提示说”入口开始”;
print reshtml % (form, who, howmany, cookie)
Result:
五》》Web(HTTP)服务器 mhtttp.py
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer #Base..提供基本的web服务HTTPServer和处理器类BaseHTTPRequestHandler
#测试地址: #服务器和网页需在同个目录,否则依情况而定
class MyHandler(BaseHTTPRequestHandler): #继承web处理器类
def do_GET(self):
try:
f = open(curdir + sep + self.path) #curdir=. sep=/ self.path=friends.html
self.send_response(200) #发送响应和html文件头,给浏览器客户端的
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():
try:
server = HTTPServer(('', 8000), MyHandler) #ip地址任意,端口8000,自定义处理器传入
print 'Welcome to the machine...',
print 'Press ^C once or twice to quit.'
server.serve_forever() #一直运行
except KeyboardInterrupt:
print '^C received, shutting down server'
server.socket.close() #请求完毕后,关闭socket资源,节省资源,具体为什么这样用,可以查查
if __name__ == "__main__":
main()
网页增加一个文件上传对话框按钮:(后面想实现一个文件上传功能,和简单的cookie功能,书上的复杂了…)
Choose file for putting
六》》读取上传文件信息,并且显示出来:friends.py (修改版)
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import cgi
import Cookie
from cStringIO import StringIO
import os
reshtml = '''Content-Type: text/html; charset=UTF-8\r\n
Friends CGI Demo (dynamic screen)
Friends list for:
%s
Your name is: %s
You have %s friends.
Get cookie %s
Upload file name: %s
Upload file content: %s
'''
#-------cookie------------------------------------
cookie = Cookie.SimpleCookie()
#-------file_upload-------------------------------
#以下我只是获取本地需要上传文件的路径;至于上传到服务器中去,需要再服务器部分做文件上传解析(自己做服务器)或者调用相应的模块中的函数去实现;
class upload(object):
def doResult(self):
MAXBYTES = 4096
filedata = ''
while len(filedata) < MAXBYTES: #read file chunks;最大1024
data = self.fp.readline()
if data == '':
break
filedata += data
self.fp.close()
return filedata
def go(self):
if form.has_key('upfile'):
upfile_dic = form['upfile'] #返回的是一个元组('upfile','[file_name]')
#self.fn = upfile2.filename or '' #利用这种upfile.filename获取的内容是空的或者说是无效的!郁闷,测试了老久...
self.fn = upfile_dic.value or ''
upfile = '/home/akaedu/python/web/%s' % self.fn #这种方式需要改为软编码;但是怎么获取文件上传文本框的路径了;form里面只是得到了最后的文件名??
if self.fn:
#self.fp = upfile.file #字符串不能这样用;不具有这样的属性
self.fp = open(upfile, 'r')
else:
self.fp = StringIO('(no data)')
else:
self.fp = StringIO('(no file)')
self.fn = ''
return self.doResult() #执行上传
def get_fn(self): #获取文件名;同时可以作为打印代码,实现测试
return self.fn
if __name__ == "__main__":
form = cgi.FieldStorage()
who = form['person'].value
howmany = form['howmany'].value
#-------cookie------------------------------------
cookie.load("person=%s;howmany=%s" % (who, howmany))
#-----call class upload---------------------------
up = upload()
content = up.go()
fn = up.get_fn()
print reshtml % (form, who, howmany, cookie, fn, content)
效果:
最后,这章还是学了不少东西,再结合点其他知识,我的广告机项目,我会给它完成,不过开发板没了;看了只能是做模拟的实验,应该是可以的!把接口做好就行,弄个简单的控制网页就行,再结合学过的cgi程序,收获不小的…