15.1屏幕抓取
屏幕抓取:通过程序下载网页,并从中提取信息的过程。
简单的屏幕抓取程序
from urllib.request import urlopen
import re
p=re.compile ('<a href="(/jobs/\\d+)/">(.*?)</a>')
text=urlopen('http://python.org/jobs').read().decode()
for url,name in p.findall(text):
print('{}({})'.format(name,url))
运行结果:
缺点:正则表达式依赖于HTML代码的细节,而不是抽象的结构,这意味着只要网页的结构发生了变化,这个程序就不管用,可以选择使用程序Tidy和XHTML解析,二是使用专门为屏幕抓取设计的Beautiful soup库。
15.1.1 Tidy和XHTML解析
1.Tidy是基于对格式不正确且不严谨的HTML进行修复的工具,它还提供了极大的配置空间,能够开关各种校正,确保文件是格式良好的。
2.获取Tidy
在UNIX系统下:
$ pip search tidy #找到可供使用的包装器
$ pip install pytidylib #安装
#对文件执行tidy并将结果打印出来
from subprocess import Popen,PIPE
text =open ('messy.html').read()
tidy=Popen('tidy ',stin=PIPE,stdout=PIPE,stderr=PIPE)
tidy.stdin.write(text.encode())
tidy.stin.close()
print(tidy.stdout.read().decode())
3.使用XHTML的原因
XTML与HTML的主要区别在于,XTML 非常的严格,要求显示的结束所有的元素,XHTML的另一个优点在于,它作为XML的方言,可以使用各种出色的工具来处理。
要对Tidy生成格式良好的XHTML进行解析,一种非常简单的方式是使用标准库模块html.parser中的HTMLParser类。
4.使用HTMLParser
回调方法 | 何时被调用 |
handle_starttag(tag,attrs) | 遇到开始标签时调用,attrs是一个由形如(name,value)的元组组成的序列 |
handle_startendtag(tag,attrs) | 遇到空标签时调用,默认分别处理开始标签和结束标签 |
handle_endtag(tag) | 遇到结束标签时调用 |
handle_data(data) | 遇到文本数据的时候调用 |
handle_charref(ref) | 遇到形如&#ref;的字符引用时调用 |
handle_entityref(name) | 遇到形如&name;的实体引用时调用 |
handle_comment(data) | 遇到注释时;只对注释内容调用 |
handle_decl(decl) | 遇到形如<!..>的声明时调用 |
handle_pi(data) | 用于处理指令 |
unkonwn_decl(data) | 遇到未知声明的时候会调用 |
屏幕抓取不需实现所有事件处理程序,,也可能无需创建整个文档的抽象表示就可找到所需的内容,只要跟踪找到目标信息就好了。
from urllib.request import urlopen
from html.parser import HTMLParser
def isjob(url):
try:
a,b,c,d=url.split('/')
except ValueError:
return False
return a == d == '' and b== 'jobs' and c.isdigit()
class Scraper(HTMLParser):
in_link=False #布尔状态量,用来跟踪自己是否处于相关的链接中,在程序处理过程中时刻更新这个值。
def handle_starttag(self,tag,attrs):
attrs=dict(attrs) #使用字典方便管理
url=attrs.get('href','')
if tag=='a' and isjob(url):
self.url=url
self.in_link=True
self.chunks=[]
def handle_data(self,data): #假定数据文本分块,需要多次调用才能获得
if self.in_link:
self.chunks.append(data)
def handle_endtag(self,tag):
if tag=='a' and self.in_link:
print("{},({})".format(''.join(self.chunks),self.url))
self.in_link=False
text=urlopen('http://python.org/jobs').read().decode()
parser=Scraper()
parser.feed(text)
parser.close()
#运行结果同上图。
15.1.2 Beautiful Soup
用于解析在Web遇到的结构不严谨的,且格式糟糕的HTML。
在UNIX 系统下安装Beautiful Soup :
$ pip install beautifulsoup4
from urllib.request import urlopen
from bs4 import BeautifulSoup
text=orlopen('http://python.org/jobs').read()
soup=BeautifulSoup(text,'html.parser')
jobs=set()
for job in soup.body.section('h2'):
jobs.add('{}({})'.format(job.a.string,job.a['href']))
print('\n'.join(sorted(jobs,key=str.lower)))
15.2 使用CGI创建动态网页
CGI(通用网关接口):作为一种标准机制,Web服务器可通过它将查询交给专有程序(如Python的程序),并以网页的形式显示查询结果。
15.2.1 准备Web服务器
前提:你能够将内容发布到Web,只需将网页和图像放到特定的目录。
$python -m http.server --cgi #通过向Python可执行文件提供开关-m,来导入并运行这个模块
Serving HTTP on 0.0.0.0 port 8000 …
这个服务器将提供运行他所在目录的文件,因此需要确保这个文件中没有机密内容。
CGI程序必须放在可通过Web访问的目录中,还必须标注为CGI 脚本,否则Web将会通过网页的形式展示其的源代码。
常用的两种方式:
1)将脚本放在子目录cgi-bin中
2)将脚本文件的扩展名指定为.cgi
可通过咨询系统管理员或ISP来了解更多。
15.2.2 添加!#行
通过添加!#行,无需显示的执行Python的解释器就能执行脚本,,但对CGI 来说,没有!#行,Web服务器讲不知道如何执行脚本,一般而言只需如下:
#!/user/bin/python (必须作为第一行,如果不可以运行确定可执行文件的位置是否准确)
(当你安装了Python2,或Python3 你需要显示的指出是Python2还是Python3)
在Windows中
#!c:\Python36\ python.exe
15.2.3设置文件权限
目的:确保大家都可以读取和执行你的脚本文件,并且只有你可以修改脚本文件
在UNIX 系统中修改文件权限的命令是chmod:
chmod 755 somescript.cgi
在浏览器中打开脚本必须使用完整的HTTP URL来打开,这样才能Web服务器取回它
通常,CGI脚本不能修改计算机上的任何文件,要让他能来修改文件必须给他权限:如果有root(系统管理员)权限,可为脚本专门创建一个用户账户,并调整需要修改文件的所有者,如果没有root权限,可设置该文件的文件权限,让系统中的所有用户都能写入这个文件。
chmod 666 editable_file.txt
使用文件模式666存在潜在的安全风险。
15.2.4CGI安全风险
在你允许CGI脚本对服务器中的文件执行写入操作的时候就有可能面临数据被破坏的风险,或者直接使用用户提供的数据作为Python的代码或者shell命令执行,就有可能执行恶意的命令,进而面临被攻击的风险。
15.2.5简单的CGI 脚本
#!/user/bin/env python
print('Content-type: text/plain')
print() #打印一个空行,以结束首部
print('Hello,world!')
将这些代码保存为文件simplel.cgi,在通过web服务器来打开,若要是使用了目录cgi-bin目录,也可以将文件命名为simple.py
15.2.6使用cgitb进行调试
#!/user/bin/env python
import cgitb;cgitb.enable()
print('Content-type:text/html\n')
print(1/0)
print('hello,world!')
在程序开发好后应关闭这种cgitb功能,因为栈跟踪页面并非供程序的普通用户查看。
15.2.7使用模块cgi
1.CGI 脚本的输入是通过HTML 的表单以键值对的方式提供给CGI 脚本的,在CGI 脚本中,可使用模块cgi中的FiledStorage类来获取这些字段,并通过一个类似于字典的接口将他们提供给脚本,要访问FiledStorage中的值,可以通过普通的键查找,其中FiledStorage中的值并不是你要的值,错误的做法:
from =cgi.FieldStorage()
name=from[‘name’]
正确的做法:
from=cgi.FieldStorage()
name=from[‘name’].value
或者使用以下的办法:
from=cgi,FieldStorage()
name=from.getvalue(‘name’,‘Unknown’) #Unkown作为一个默认值,如果不提供的话,默认值将为None,在字段没有值得时候使用默认值。
#!/user/bin/env python
import cgi
from=cgi.FieldStorage()
name=from.getvalue('nmae','world')
print('Content-type:text/plain\n')
print('Hello,{}!'.format(name))