Python Web

爬虫

Robots 协议

Robots 协议指定了一个网站可以爬取的信息,例如: http://www.taobao.com/robots.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
User-agent:  Baiduspider
Allow: /article
Allow: /oshtml
Allow: /ershou
Allow: /$
Disallow: /product/
Disallow: /

User-Agent: *
Disallow: /

# 站点信息
Sitemap: ...

使用爬虫技术,需要注意应:

  • 要伪装User-Agent,且需要多个,随机选取
  • 对参数进行URL编码
  • 注意是否是通过AJAX传输数据
  • 保存服务器下发的Cookies

URL Lib 包

urllib.request,负责读写 url。 urllib.error,定义错误与异常。 urllib.parse,url参数的编码解码。 urllib.robotparser,用于分析robots.txt文件。

简单的读写URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from urllib.request import urlopen, Request

# 直接打开一个url,Request对象,返回HTTPResponse对象,用法类似文件
res = urlopen(url, data)

# 构造Request
req = Request(url, headers={
'User-agent': 'user agent'
})
# 或 req.add_header('', '')
res = urlopen(req)

# 查看结果
res.closed # 查看是否关闭,False
with res:
res.status # 状态码
res.reason # 状态
res.getrul() # 真正的URL,例如重定向后的URL
res.info() # headers
res.read() # 返回读取的内容
res.closed # 查看是否关闭,True

URL参数的编码解码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib.request import parse

d = {
'id': 1,
'name': 'auther',
}
# url编码
arg = parse.urlencode(d)
# url解码
d = parse.unquote(arg)
# 使用
url = f"http://...?{arg}" # GET
# 或 urlopen(req, arg.encode()) POST

HTTP 实验网站

AJAX 与 HTTPS

在Chrome浏览器里,进入开发者选项,选择XHR分类,查看异步请求。利用其中的AJAX接口进行数据请求。

HTTPS是由权威机构颁发的证书,颁发的证书文件需要事先上传至被认证的服务器上。当用户访问网站时,用户浏览器会首先得到该网站的服务器证书,用户拿到证书后进行验证,进而判断通信是否安全。

在爬虫中,我们会遇到拥有HTTPS但是不信任的网站,因此要尽量忽略HTTPS以减少工作量。

使用SSL模块忽略HTTPS:

1
2
3
4
5
6
import ssl

# 忽略不信任的证书
context = ssl._create_unverified_context()
with urlopen(req, context = context) as res:
pass

urllib 3

urllib 3库是一个第三方库,提供了例如连接池管理等功能。

1
pip install urllib3

使用:

1
2
3
4
5
6
7
8
9
10
import urllib3

# 打开一个 URL 池管理器
with urllib3.PoolManager() as http:
# http.urlopen()
resp = http.request()
# resp.status
# resp.reason
# resp.headers
# resp.data

requests 库

requests库是基于urllib3库的,而且提供了更加友好的API使用。

1
pip install requests

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests

resp = requests.request('GET', url, headers={
'User-Agent': ua
}
)

with resp:
# resp.url
# resp.status_code
# resp.request.headers
# resp.text
# resp.cookies

使用带Cookie的访问:

1
2
3
4
5
with request.Session() as session:
for url in urls:
resp = session.get(url, headers={'',''})
with resp:
pass

XPATH 技术

XPath是用来在XML中查找信息的语言。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="ISO-8859-1"?>

<bookstore>

<book>
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>

</bookstore>

在XPath中定义了节点:

  • 元素:<title lang="en">Harry Potter</title>
  • 属性:lang="en"
  • 文本:
  • 命名空间:<?xml version="1.0" encoding="ISO-8859-1"?>
  • 处理指令
  • 注释
  • 文档节点:<bookstore>

也包含节点关系,如:父,子,兄弟,所有祖先,所有后代。

在XPath中,节点之间的父子关系可以用表达式表示:

  • nodename:选取此节点的所有子节点
  • /:根节点或分隔符
  • //:后继节点,不考虑路径
  • .:当前节点
  • ..:父节点
  • @:属性
  • |:选取多个路径

谓语是用于按照索引选择子节点的工具:

  • [1]:第一个元素
  • [last()]:最后一个元素
  • [position()<3]:前两个元素
  • [@lang]:拥有lang属性的元素
  • [@lang='eng']:满足条件的元素
  • [price>10]:元素值大于10的元素

通配符:

  • *:任何元素节点
  • @*:任何属性节点
  • node():任何类型节点

轴可定义相对于当前节点的节点集:

  • ancestor:选取当前节点的所有祖先(父、祖父等)。
  • ancestor-or-self:选取当前节点的所有祖先(父、祖父等)以及当前节点本身。
  • attribute:选取当前节点的所有属性。
  • child:选取当前节点的所有子元素。
  • descendant:选取当前节点的所有后代元素(子、孙等)。
  • descendant-or-self:选取当前节点的所有后代元素(子、孙等)以及当前节点本身。
  • following:选取文档中当前节点的结束标签之后的所有节点。
  • namespace:选取当前节点的所有命名空间节点。
  • parent:选取当前节点的父节点。
  • preceding:选取文档中当前节点的开始标签之前的所有节点。
  • preceding-sibling:选取当前节点之前的所有同级节点。
  • self:选取当前节点。

使用轴可以选取某些节点:

  • child::book:选取所有属于当前节点的子元素的 book 节点。
  • child::text():选取当前节点的所有文本子节点。

安装 lxml 模块:

1
2
3
4
# linux 需要依赖
sudo apt-get install libxml2-dev libxslt-dev
# windows 不需要
pip install lxml

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from lxml import etree

# 构建标签
root = etree.Element('html')
body = etree.Element('body')
root.append(body)

# 打印HTML
print(etree.tostring(root))
print(etree.tostring(
root,
pretty_print=True
).decode())

# 添加子元素
sub = etree.SubElement(body, 'child1')
sub = etree.SubElement(body, 'child2')

# 解析HTML
etree.HTML(text)
a_node.xpath('xpath 路径')

在Chrome使用XPath工具:在选定的标签上右键->Copy->XPath,并根据给定的内容进行修改。也可以使用插件ChroPath调试。

在分析选定标签的时候,可以优先找id属性,其次class属性。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from lxml import etree
import requests

url = 'https://moive.douban.com'
ua = ''
with requests.get(
url,
headers={'User-Agent': ua}
) as response:
# HTML 内容
content = response.txt
# 解析为 DOM
html = etree.HTML(content)
# 使用XPath得到内容
titles = html.xpath("//div[@class='villboard-bd']//tr/td/a/text()")
for item in titles:
print(item)

XPath 语言

Spider

Web

Django

Flask

Cherrypy

Tornado

官方文档 Github

优势:

  • 适合构建微服务。

用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")

def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])

if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()

分布式系统 Celery

特点:

  • 分布式系统
  • 异步任务队列
  • 支持任务调度

工作:

  • 执行异步任务
  • 执行定时任务

需要消息中间件:RabbitMQ 或 Redis

基本使用

1
2
3
4
5
6
7
tasks.py
from celery import Celery
app = Celery("task_name", backend="redis://local:6379/2", broker="redis://local:6379/1")

@app.task # 变为异步
def add(x, y):
pass

启动 worker

1
celery worker -A tasks -l INFO

使用

1
2
from tasks import add
x = add.delay(1,2)

配置文件

目录:

  • celery_app
    • celeryconfig.app
    • task1.py
    • task2.py

app.py

1
2
app = Celery("task_name")
app.config_from_object("celery_app.celeryconfig")

celeryconfig.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BROKER_URL = ''
CELERY_RESULT_BACKEND = '' # 结果存储到数据库
CELERY_TIMEZONE = ''
CELERY_IMPORTS = (
'celery_app.task1',
'celery_app.task2',
)
CELERY_SCHEDULE = {
'task1': {
'task': 'celery_app.task1.add',
'schedule': timedelta(seconds=10),
'args': (2, 3),
}
}

结合 Django

监控 Flower

进程管理 Supervisor

模板

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
def make_app():
return tornado.web.Application(
# 路由配置
[
(r"/", MainHandler),
],
# 渲染模板路径
template_path = os.path.join(
os.path.dirname(__file__), "template"
),
# 开启 Debug
debug = True
)

控制器

1
2
3
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", msg="")

index.html

1
{{msg}}

异步服务器与客户端

1
2
3
4
app = tornado.web.Application()
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()

接口测试