0%

Python

2022 年最新 Python3 网络爬虫教程

大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

教程请移步:

【2022 版】Python3 网络爬虫学习教程

如下为原文。

审时度势

PySpider 是一个我个人认为非常方便并且功能强大的爬虫框架,支持多线程爬取、JS 动态解析,提供了可操作界面、出错重试、定时爬取等等的功能,使用非常人性化。 本篇内容通过跟我做一个好玩的 PySpider 项目,来理解 PySpider 的运行流程。

招兵买马

具体的安装过程请查看本节讲述 安装 嗯,安装好了之后就与我大干一番吧。

鸿鹄之志

我之前写过的一篇文章 抓取淘宝 MM 照片 由于网页改版,爬取过程中需要的 URL 需要 JS 动态解析生成,所以之前用的 urllib2 不能继续使用了,在这里我们利用 PySpider 重新实现一下。 所以现在我们需要做的是抓取淘宝 MM 的个人信息和图片存储到本地。

审时度势

爬取目标网站:https://mm.taobao.com/json/request_top_list.htm?page=1,大家打开之后可以看到许多淘宝 MM 的列表。 列表有多少? https://mm.taobao.com/json/request_top_list.htm?page=10000,第 10000 页都有,看你想要多少。我什么也不知道。 随机点击一位 MM 的姓名,可以看到她的基本资料。 QQ20160326-4@2x 可以看到图中有一个个性域名,我们复制到浏览器打开。mm.taobao.com/tyy6160 QQ20160326-5@2x 嗯,往下拖,海量的 MM 图片都在这里了,怎么办你懂得,我们要把她们的照片和个人信息都存下来。 P.S. 注意图中进度条!你猜有多少图片~

利剑出鞘

安装成功之后,跟我一步步地完成一个网站的抓取,你就会明白 PySpider 的基本用法了。 命令行下执行

1
pyspider all

这句命令的意思是,运行 pyspider 并 启动它的所有组件。 E6632A0A-9067-4B97-93A2-5DEF23FB4CD8 可以发现程序已经正常启动,并在 5000 这个端口运行。

一触即发

接下来在浏览器中输入 http://localhost:5000,可以看到 PySpider 的主界面,点击右下角的 Create,命名为 taobaomm,当然名称你可以随意取,继续点击 Create。 QQ20160325-0@2x 这样我们会进入到一个爬取操作的页面。 QQ20160325-1@2x 整个页面分为两栏,左边是爬取页面预览区域,右边是代码编写区域。下面对区块进行说明: 左侧绿色区域:这个请求对应的 JSON 变量,在 PySpider 中,其实每个请求都有与之对应的 JSON 变量,包括回调函数,方法名,请求链接,请求数据等等。 绿色区域右上角 Run:点击右上角的 run 按钮,就会执行这个请求,可以在左边的白色区域出现请求的结果。 左侧 enable css selector helper: 抓取页面之后,点击此按钮,可以方便地获取页面中某个元素的 CSS 选择器。 左侧 web: 即抓取的页面的实时预览图。 左侧 html: 抓取页面的 HTML 代码。 左侧 follows: 如果当前抓取方法中又新建了爬取请求,那么接下来的请求就会出现在 follows 里。 左侧 messages: 爬取过程中输出的一些信息。 右侧代码区域: 你可以在右侧区域书写代码,并点击右上角的 Save 按钮保存。 右侧 WebDAV Mode: 打开调试模式,左侧最大化,便于观察调试。

乘胜追击

依然是上一节的那个网址,https://mm.taobao.com/json/request_top_list.htm?page=1,其中 page 参数代表页码。所以我们暂时抓取前 30 页。页码到最后可以随意调整。 首先我们定义基地址,然后定义爬取的页码和总页码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pyspider.libs.base_handler import *


class Handler(BaseHandler):
crawl_config = {
}

def __init__(self):
self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
self.page_num = 1
self.total_num = 30

@every(minutes=24 * 60)
def on_start(self):
while self.page_num <= self.total_num:
url = self.base_url + str(self.page_num)
print url
self.crawl(url, callback=self.index_page)
self.page_num += 1

@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a[href^="http"]').items():
self.crawl(each.attr.href, callback=self.detail_page)

@config(priority=2)
def detail_page(self, response):
return {
"url": response.url,
"title": response.doc('title').text(),
}

点击 save 保存代码,然后点击左边的 run,运行代码。 QQ20160325-2@2x 运行后我们会发现 follows 出现了 30 这个数字,说明我们接下来有 30 个新请求,点击可查看所有爬取列表。另外控制台也有输出,将所有要爬取的 URL 打印了出来。 然后我们点击左侧任意一个绿色箭头,可以继续爬取这个页面。例如点击第一个 URL,来爬取这个 URL QQ20160325-3@2x 点击之后,再查看下方的 web 页面,可以预览实时页面,这个页面被我们爬取了下来,并且回调到 index_page 函数来处理,目前 index_page 函数我们还没有处理,所以是继续构件了所有的链接请求。 QQ20160325-4@2x 好,接下来我们怎么办?当然是进入到 MM 到个人页面去爬取了。

如火如荼

爬取到了 MM 的列表,接下来就要进入到 MM 详情页了,修改 index_page 方法。

1
2
3
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page)

其中 response 就是刚才爬取的列表页,response 其实就相当于列表页的 html 代码,利用 doc 函数,其实是调用了 PyQuery,用 CSS 选择器得到每一个 MM 的链接,然后重新发起新的请求。 比如,我们这里拿到的 each.attr.href 可能是 mm.taobao.com/self/model_card.htm?user_id=687471686,在这里继续调用了 crawl 方法,代表继续抓取这个链接的详情。

1
self.crawl(each.attr.href, callback=self.detail_page)

然后回调函数就是 detail_page,爬取的结果会作为 response 变量传过去。detail_page 接到这个变量继续下面的分析。 QQ20160325-7@2x 好,我们继续点击 run 按钮,开始下一个页面的爬取。得到的结果是这样的。 QQ20160325-5@2x 哦,有些页面没有加载出来,这是为什么? 在之前的文章说过,这个页面比较特殊,右边的页面使用 JS 渲染生成的,而普通的抓取是不能得到 JS 渲染后的页面的,这可麻烦了。 然而,幸运的是,PySpider 提供了动态解析 JS 的机制。 友情提示:可能有的小伙伴不知道 PhantomJS,可以参考 爬虫 JS 动态解析 因为我们在前面装好了 PhantomJS,所以,这时候就轮到它来出场了。在最开始运行 PySpider 的时候,使用了pyspider all命令,这个命令是把 PySpider 所有的组件启动起来,其中也包括 PhantomJS。 所以我们代码怎么改呢?很简单。

1
2
3
def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')

只是简单地加了一个 fetch_type=’js’,点击绿色的返回箭头,重新运行一下。 可以发现,页面已经被我们成功加载出来了,简直不能更帅! QQ20160325-9@2x 看下面的个性域名,所有我们需要的 MM 图片都在那里面了,所以我们需要继续抓取这个页面。

胜利在望

好,继续修改 detail_page 方法,然后增加一个 domain_page 方法,用来处理每个 MM 的个性域名。

1
2
3
4
5
6
7
def detail_page(self, response):
domain = 'https:' + response.doc('.mm-p-domain-info li > span').text()
print domain
self.crawl(domain, callback=self.domain_page)

def domain_page(self, response):
pass

好,继续重新 run,预览一下页面,终于,我们看到了 MM 的所有图片。 QQ20160326-0@2x 嗯,你懂得!

只欠东风

好,照片都有了,那么我们就偷偷地下载下来吧~ 完善 domain_page 代码,实现保存简介和遍历保存图片的方法。 在这里,PySpider 有一个特点,所有的 request 都会保存到一个队列中,并具有去重和自动重试机制。所以,我们最好的解决方法是,把每张图片的请求都写成一个 request,然后成功后用文件写入即可,这样会避免图片加载不全的问题。 曾经在之前文章写过图片下载和文件夹创建的过程,在这里就不多赘述原理了,直接上写好的工具类,后面会有完整代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os

class Deal:
def __init__(self):
self.path = DIR_PATH
if not self.path.endswith('/'):
self.path = self.path + '/'
if not os.path.exists(self.path):
os.makedirs(self.path)

def mkDir(self, path):
path = path.strip()
dir_path = self.path + path
exists = os.path.exists(dir_path)
if not exists:
os.makedirs(dir_path)
return dir_path
else:
return dir_path

def saveImg(self, content, path):
f = open(path, 'wb')
f.write(content)
f.close()

def saveBrief(self, content, dir_path, name):
file_name = dir_path + "/" + name + ".txt"
f = open(file_name, "w+")
f.write(content.encode('utf-8'))

def getExtension(self, url):
extension = url.split('.')[-1]
return extension

这里面包含了四个方法。

mkDir:创建文件夹,用来创建 MM 名字对应的文件夹。 saveBrief: 保存简介,保存 MM 的文字简介。 saveImg: 传入图片二进制流以及保存路径,存储图片。 getExtension: 获得链接的后缀名,通过图片 URL 获得。

然后在 domain_page 中具体实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def domain_page(self, response):
name = response.doc('.mm-p-model-info-left-top dd > a').text()
dir_path = self.deal.mkDir(name)
brief = response.doc('.mm-aixiu-content').text()
if dir_path:
imgs = response.doc('.mm-aixiu-content img').items()
count = 1
self.deal.saveBrief(brief, dir_path, name)
for img in imgs:
url = img.attr.src
if url:
extension = self.deal.getExtension(url)
file_name = name + str(count) + '.' + extension
count += 1
self.crawl(img.attr.src, callback=self.save_img,
save={'dir_path': dir_path, 'file_name': file_name})

def save_img(self, response):
content = response.content
dir_path = response.save['dir_path']
file_name = response.save['file_name']
file_path = dir_path + '/' + file_name
self.deal.saveImg(content, file_path)

以上方法首先获取了页面的所有文字,然后调用了 saveBrief 方法存储简介。 然后遍历了 MM 所有的图片,并通过链接获取后缀名,和 MM 的姓名以及自增计数组合成一个新的文件名,调用 saveImg 方法保存图片。

炉火纯青

好,基本的东西都写好了。 接下来。继续完善一下代码。第一版本完成。 版本一功能:按照淘宝 MM 姓名分文件夹,存储 MM 的 txt 文本简介以及所有美图至本地。 可配置项:

  • PAGE_START: 列表开始页码
  • PAGE_END: 列表结束页码
  • DIR_PATH: 资源保存路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2016-03-25 00:59:45
# Project: taobaomm

from pyspider.libs.base_handler import *

PAGE_START = 1
PAGE_END = 30
DIR_PATH = '/var/py/mm'


class Handler(BaseHandler):
crawl_config = {
}

def __init__(self):
self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
self.page_num = PAGE_START
self.total_num = PAGE_END
self.deal = Deal()

def on_start(self):
while self.page_num <= self.total_num:
url = self.base_url + str(self.page_num)
self.crawl(url, callback=self.index_page)
self.page_num += 1

def index_page(self, response):
for each in response.doc('.lady-name').items():
self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')

def detail_page(self, response):
domain = response.doc('.mm-p-domain-info li > span').text()
if domain:
page_url = 'https:' + domain
self.crawl(page_url, callback=self.domain_page)

def domain_page(self, response):
name = response.doc('.mm-p-model-info-left-top dd > a').text()
dir_path = self.deal.mkDir(name)
brief = response.doc('.mm-aixiu-content').text()
if dir_path:
imgs = response.doc('.mm-aixiu-content img').items()
count = 1
self.deal.saveBrief(brief, dir_path, name)
for img in imgs:
url = img.attr.src
if url:
extension = self.deal.getExtension(url)
file_name = name + str(count) + '.' + extension
count += 1
self.crawl(img.attr.src, callback=self.save_img,
save={'dir_path': dir_path, 'file_name': file_name})

def save_img(self, response):
content = response.content
dir_path = response.save['dir_path']
file_name = response.save['file_name']
file_path = dir_path + '/' + file_name
self.deal.saveImg(content, file_path)


import os

class Deal:
def __init__(self):
self.path = DIR_PATH
if not self.path.endswith('/'):
self.path = self.path + '/'
if not os.path.exists(self.path):
os.makedirs(self.path)

def mkDir(self, path):
path = path.strip()
dir_path = self.path + path
exists = os.path.exists(dir_path)
if not exists:
os.makedirs(dir_path)
return dir_path
else:
return dir_path

def saveImg(self, content, path):
f = open(path, 'wb')
f.write(content)
f.close()

def saveBrief(self, content, dir_path, name):
file_name = dir_path + "/" + name + ".txt"
f = open(file_name, "w+")
f.write(content.encode('utf-8'))

def getExtension(self, url):
extension = url.split('.')[-1]
return extension

粘贴到你的 PySpider 中运行吧~ 其中有一些知识点,我会在后面作详细的用法总结。大家可以先体会一下代码。 QQ20160326-1@2x 保存之后,点击下方的 run,你会发现,海量的 MM 图片已经涌入你的电脑啦~ QQ20160326-2@2x QQ20160326-3@2x 需要解释?需要我也不解释!

项目代码

TaobaoMM - GitHub

尚方宝剑

如果想了解 PySpider 的更多内容,可以查看官方文档。 官方文档

Python

2022 年最新 Python3 网络爬虫教程

大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

教程请移步:

【2022 版】Python3 网络爬虫学习教程

如下为原文。

前言

你是否觉得 XPath 的用法多少有点晦涩难记呢? 你是否觉得 BeautifulSoup 的语法多少有些悭吝难懂呢? 你是否甚至还在苦苦研究正则表达式却因为少些了一个点而抓狂呢? 你是否已经有了一些前端基础了解选择器却与另外一些奇怪的选择器语法混淆了呢? 嗯,那么,前端大大们的福音来了,PyQuery 来了,乍听名字,你一定联想到了 jQuery,如果你对 jQuery 熟悉,那么 PyQuery 来解析文档就是不二之选!包括我在内! PyQuery 是 Python 仿照 jQuery 的严格实现。语法与 jQuery 几乎完全相同,所以不用再去费心去记一些奇怪的方法了。 天下竟然有这等好事?我都等不及了!

安装

有这等神器还不赶紧安装了!来!

1
pip install pyquery

还是原来的配方,还是熟悉的味道。

参考来源

本文内容参考官方文档,更多内容,大家可以去官方文档学习,毕竟那里才是最原汁原味的。 目前版本 1.2.4 (2016/3/24) 官方文档

简介

pyquery allows you to make jquery queries on xml documents. The API is as much as possible the similar to jquery. pyquery uses lxml for fast xml and html manipulation. This is not (or at least not yet) a library to produce or interact with javascript code. I just liked the jquery API and I missed it in python so I told myself “Hey let’s make jquery in python”. This is the result. It can be used for many purposes, one idea that I might try in the future is to use it for templating with pure http templates that you modify using pyquery. I can also be used for web scrapping or for theming applications with Deliverance.

pyquery 可让你用 jQuery 的语法来对 xml 进行操作。这I和 jQuery 十分类似。如果利用 lxml,pyquery 对 xml 和 html 的处理将更快。 这个库不是(至少还不是)一个可以和 JavaScript交互的代码库,它只是非常像 jQuery API 而已。

初始化

在这里介绍四种初始化方式。 (1)直接字符串

1
2
from pyquery import PyQuery as pq
doc = pq("<html></html>")

pq 参数可以直接传入 HTML 代码,doc 现在就相当于 jQuery 里面的 $ 符号了。 (2)lxml.etree

1
2
from lxml import etree
doc = pq(etree.fromstring("<html></html>"))

可以首先用 lxml 的 etree 处理一下代码,这样如果你的 HTML 代码出现一些不完整或者疏漏,都会自动转化为完整清晰结构的 HTML代码。 (3)直接传URL

1
2
from pyquery import PyQuery as pq
doc = pq('http://www.baidu.com')

这里就像直接请求了一个网页一样,类似用 urllib2 来直接请求这个链接,得到 HTML 代码。 (4)传文件

1
2
from pyquery import PyQuery as pq
doc = pq(filename='hello.html')

可以直接传某个路径的文件名。

快速体验

现在我们以本地文件为例,传入一个名字为 hello.html 的文件,文件内容为

1
2
3
4
5
6
7
8
9
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>

编写如下程序

1
2
3
4
5
6
7
from pyquery import PyQuery as pq
doc = pq(filename='hello.html')
print doc.html()
print type(doc)
li = doc('li')
print type(li)
print li.text()

运行结果

1
2
3
4
5
6
7
8
9
10
11
    <ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>

<class 'pyquery.pyquery.PyQuery'>
<class 'pyquery.pyquery.PyQuery'>
first item second item third item fourth item fifth item

看,回忆一下 jQuery 的语法,是不是运行结果都是一样的呢? 在这里我们注意到了一点,PyQuery 初始化之后,返回类型是 PyQuery,利用了选择器筛选一次之后,返回结果的类型依然还是 PyQuery,这简直和 jQuery 如出一辙,不能更赞!然而想一下 BeautifulSoup 和 XPath 返回的是什么?列表!一种不能再进行二次筛选(在这里指依然利用 BeautifulSoup 或者 XPath 语法)的对象! 然而比比 PyQuery,哦我简直太爱它了!

属性操作

你可以完全按照 jQuery 的语法来进行 PyQuery 的操作。

1
2
3
4
5
6
from pyquery import PyQuery as pq

p = pq('<p id="hello" class="hello"></p>')('p')
print p.attr("id")
print p.attr("id", "plop")
print p.attr("id", "hello")

运行结果

1
2
3
hello
<p id="plop" class="hello"/>
<p id="hello" class="hello"/>

再来一发

1
2
3
4
5
6
7
from pyquery import PyQuery as pq

p = pq('<p id="hello" class="hello"></p>')('p')
print p.addClass('beauty')
print p.removeClass('hello')
print p.css('font-size', '16px')
print p.css({'background-color': 'yellow'})

运行结果

1
2
3
4
<p id="hello" class="hello beauty"/>
<p id="hello" class="beauty"/>
<p id="hello" class="beauty" style="font-size: 16px"/>
<p id="hello" class="beauty" style="font-size: 16px; background-color: yellow"/>

依旧是那么优雅与自信! 在这里我们发现了,这是一连串的操作,而 p 是一直在原来的结果上变化的。 因此执行上述操作之后,p 本身也发生了变化。

DOM操作

同样的原汁原味的 jQuery 语法

1
2
3
4
5
6
7
8
9
10
11
from pyquery import PyQuery as pq

p = pq('<p id="hello" class="hello"></p>')('p')
print p.append(' check out <a href="http://reddit.com/r/python"><span>reddit</span></a>')
print p.prepend('Oh yes!')
d = pq('<div class="wrap"><div id="test"><a href="http://cuiqingcai.com">Germy</a></div></div>')
p.prependTo(d('#test'))
print p
print d
d.empty()
print d

运行结果

1
2
3
4
5
<p id="hello" class="hello"> check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p>
<p id="hello" class="hello">Oh yes! check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p>
<p id="hello" class="hello">Oh yes! check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p>
<div class="wrap"><div id="test"><p id="hello" class="hello">Oh yes! check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p><a href="http://cuiqingcai.com">Germy</a></div></div>
<div class="wrap"/>

这不需要多解释了吧。 DOM 操作也是与 jQuery 如出一辙。

遍历

遍历用到 items 方法返回对象列表,或者用 lambda

1
2
3
4
5
6
7
from pyquery import PyQuery as pq
doc = pq(filename='hello.html')
lis = doc('li')
for li in lis.items():
print li.html()

print lis.each(lambda e: e)

运行结果

1
2
3
4
5
6
7
8
9
10
first item
<a href="link2.html">second item</a>
<a href="link3.html"><span class="bold">third item</span></a>
<a href="link4.html">fourth item</a>
<a href="link5.html">fifth item</a>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>

不过最常用的还是 items 方法

网页请求

PyQuery 本身还有网页请求功能,而且会把请求下来的网页代码转为 PyQuery 对象。

1
2
3
from pyquery import PyQuery as pq
print pq('http://cuiqingcai.com/', headers={'user-agent': 'pyquery'})
print pq('http://httpbin.org/post', {'foo': 'bar'}, method='post', verify=True)

感受一下,GET,POST,样样通。

Ajax

PyQuery 同样支持 Ajax 操作,带有 get 和 post 方法,不过不常用,一般我们不会用 PyQuery 来做网络请求,仅仅是用来解析。 PyQueryAjax

API

最后少不了的,API大放送。 API 原汁原味最全的API,都在里面了!如果你对 jQuery 语法不熟,强烈建议先学习下 jQuery,再回来看 PyQuery,你会感到异常亲切!

结语

用完了 PyQuery,我已经深深爱上了他! 你呢?

Python

2022 年最新 Python3 网络爬虫教程

大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

教程请移步:

【2022 版】Python3 网络爬虫学习教程

原文

前言

前面我们介绍了 BeautifulSoup 的用法,这个已经是非常强大的库了,不过还有一些比较流行的解析库,例如 lxml,使用的是 Xpath 语法,同样是效率比较高的解析方法。如果大家对 BeautifulSoup 使用不太习惯的话,可以尝试下 Xpath。

参考来源

lxml用法源自 lxml python 官方文档,更多内容请直接参阅官方文档,本文对其进行翻译与整理。 lxml XPath语法参考 w3school w3school

视频资源

如果你对 XPath 不熟悉的话,可以看下这个视频资源: web端功能自动化定位元素

安装

1
pip install lxml

利用 pip 安装即可

XPath语法

XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。

节点关系

(1)父(Parent) 每个元素以及属性都有一个父。 在下面的例子中,book 元素是 title、author、year 以及 price 元素的父:

1
2
3
4
5
6
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>

(2)子(Children) 元素节点可有零个、一个或多个子。 在下面的例子中,title、author、year 以及 price 元素都是 book 元素的子:

1
2
3
4
5
6
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>

(3)同胞(Sibling) 拥有相同的父的节点 在下面的例子中,title、author、year 以及 price 元素都是同胞:

1
2
3
4
5
6
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>

(4)先辈(Ancestor) 某节点的父、父的父,等等。 在下面的例子中,title 元素的先辈是 book 元素和 bookstore 元素:

1
2
3
4
5
6
7
8
9
10
<bookstore>

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

</bookstore>

(5)后代(Descendant) 某个节点的子,子的子,等等。 在下面的例子中,bookstore 的后代是 book、title、author、year 以及 price 元素:

1
2
3
4
5
6
7
8
9
10
<bookstore>

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

</bookstore>

选取节点

XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。

下面列出了最有用的路径表达式:

表达式

描述

nodename

选取此节点的所有子节点。

/

从根节点选取。

//

从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。

.

选取当前节点。

..

选取当前节点的父节点。

@

选取属性。

实例 在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式

结果

bookstore

选取 bookstore 元素的所有子节点。

/bookstore

选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!

bookstore/book

选取属于 bookstore 的子元素的所有 book 元素。

//book

选取所有 book 子元素,而不管它们在文档中的位置。

bookstore//book

选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。

//@lang

选取名为 lang 的所有属性。

谓语(Predicates)

谓语用来查找某个特定的节点或者包含某个指定的值的节点。 谓语被嵌在方括号中。 实例 在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式

结果

/bookstore/book[1]

选取属于 bookstore 子元素的第一个 book 元素。

/bookstore/book[last()]

选取属于 bookstore 子元素的最后一个 book 元素。

/bookstore/book[last()-1]

选取属于 bookstore 子元素的倒数第二个 book 元素。

/bookstore/book[position()<3]

选取最前面的两个属于 bookstore 元素的子元素的 book 元素。

//title[@lang]

选取所有拥有名为 lang 的属性的 title 元素。

//title[@lang=’eng’]

选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。

/bookstore/book[price>35.00]

选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。

/bookstore/book[price>35.00]/title

选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符

描述

*

匹配任何元素节点。

@*

匹配任何属性节点。

node()

匹配任何类型的节点。

实例 在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式

结果

/bookstore/*

选取 bookstore 元素的所有子元素。

//*

选取文档中的所有元素。

//title[@*]

选取所有带有属性的 title 元素。

选取若干路径

通过在路径表达式中使用“|”运算符,您可以选取若干个路径。 实例 在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式

结果

//book/title | //book/price

选取 book 元素的所有 title 和 price 元素。

//title | //price

选取文档中的所有 title 和 price 元素。

/bookstore/book/title | //price

选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

XPath 运算符

下面列出了可用在 XPath 表达式中的运算符:

运算符

描述

实例

返回值

|

计算两个节点集

//book | //cd

返回所有拥有 book 和 cd 元素的节点集

+

加法

6 + 4

10

-

减法

6 - 4

2

*

乘法

6 * 4

24

div

除法

8 div 4

2

\=

等于

price=9.80

如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。

!=

不等于

price!=9.80

如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。

<

小于

price<9.80

如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。

<=

小于或等于

price<=9.80

如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。

>

大于

price>9.80

如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。

>=

大于或等于

price>=9.80

如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。

or

price=9.80 or price=9.70

如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。

and

price>9.00 and price<9.90

如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。

mod

计算除法的余数

5 mod 2

1

lxml用法

初步使用

首先我们利用它来解析 HTML 代码,先来一个小例子来感受一下它的基本用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from lxml import etree
text = '''
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)

首先我们使用 lxml 的 etree 库,然后利用 etree.HTML 初始化,然后我们将其打印出来。 其中,这里体现了 lxml 的一个非常实用的功能就是自动修正 html 代码,大家应该注意到了,最后一个 li 标签,其实我把尾标签删掉了,是不闭合的。不过,lxml 因为继承了 libxml2 的特性,具有自动修正 HTML 代码的功能。 所以输出结果是这样的

1
2
3
4
5
6
7
8
9
10
11
12
<html><body>
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>

</body></html>

不仅补全了 li 标签,还添加了 body,html 标签。

文件读取

除了直接读取字符串,还支持从文件读取内容。比如我们新建一个文件叫做 hello.html,内容为

1
2
3
4
5
6
7
8
9
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>

利用 parse 方法来读取文件。

1
2
3
4
from lxml import etree
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)

同样可以得到相同的结果。

XPath实例测试

依然以上一段程序为例 (1)获取所有的

  • 标签

    1
    2
    3
    4
    5
    6
    7
    8
    from lxml import etree
    html = etree.parse('hello.html')
    print type(html)
    result = html.xpath('//li')
    print result
    print len(result)
    print type(result)
    print type(result[0])

    运行结果

    1
    2
    3
    4
    5
    <type 'lxml.etree._ElementTree'>
    [<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
    5
    <type 'list'>
    <type 'lxml.etree._Element'>

    可见,etree.parse 的类型是 ElementTree,通过调用 xpath 以后,得到了一个列表,包含了 5 个

  • 元素,每个元素都是 Element 类型 (2)获取
  • 标签的所有 class

    1
    2
    result = html.xpath('//li/@class')
    print result

    运行结果

    1
    ['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']

    (3)获取

  • 标签下 href 为 link1.html 的 标签

    1
    2
    result = html.xpath('//li/a[@href="link1.html"]')
    print result

    运行结果

    1
    [<Element a at 0x10ffaae18>]

    (4)获取

  • 标签下的所有 标签 注意这么写是不对的

    1
    result = html.xpath('//li/span')

    因为 / 是用来获取子元素的,而 并不是

  • 的子元素,所以,要用双斜杠

    1
    2
    result = html.xpath('//li//span')
    print result

    运行结果

    1
    [<Element span at 0x10d698e18>]

    (5)获取

  • 标签下的所有 class,不包括
  • 1
    2
    result = html.xpath('//li/a//@class')
    print result

    运行结果

    1
    ['blod']

    (6)获取最后一个

  • 的 href

    1
    2
    result = html.xpath('//li[last()]/a/@href')
    print result

    运行结果

    1
    ['link5.html']

    (7)获取倒数第二个元素的内容

    1
    2
    result = html.xpath('//li[last()-1]/a')
    print result[0].text

    运行结果

    1
    fourth item

    (8)获取 class 为 bold 的标签名

    1
    2
    result = html.xpath('//*[@class="bold"]')
    print result[0].tag

    运行结果

    1
    span

    通过以上实例的练习,相信大家对 XPath 的基本用法有了基本的了解。也可以利用 text 方法来获取元素的内容。 大家多加练习!

    结语

    XPath 是一个非常好用的解析方法,同时也作为爬虫学习的基础,在后面的 selenium 以及 scrapy 框架中都会涉及到这部分知识,希望大家可以把它的语法掌握清楚,为后面的深入研究做好铺垫。

  • Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    如下为原文。

    前言

    在上一节我们学习了 PhantomJS 的基本用法,归根结底它是一个没有界面的浏览器,而且运行的是 JavaScript 脚本,然而这就能写爬虫了吗?这又和Python有什么关系?说好的Python爬虫呢?库都学完了你给我看这个?客官别急,接下来我们介绍的这个工具,统统解决掉你的疑惑。

    简介

    Selenium 是什么?一句话,自动化测试工具。它支持各种浏览器,包括 Chrome,Safari,Firefox 等主流界面式浏览器,如果你在这些浏览器里面安装一个 Selenium 的插件,那么便可以方便地实现Web界面的测试。换句话说叫 Selenium 支持这些浏览器驱动。话说回来,PhantomJS不也是一个浏览器吗,那么 Selenium 支持不?答案是肯定的,这样二者便可以实现无缝对接了。 然后又有什么好消息呢?Selenium支持多种语言开发,比如 Java,C,Ruby等等,有 Python 吗?那是必须的!哦这可真是天大的好消息啊。 嗯,所以呢?安装一下 Python 的 Selenium 库,再安装好 PhantomJS,不就可以实现 Python+Selenium+PhantomJS 的无缝对接了嘛!PhantomJS 用来渲染解析JS,Selenium 用来驱动以及与 Python 的对接,Python 进行后期的处理,完美的三剑客! 有人问,为什么不直接用浏览器而用一个没界面的 PhantomJS 呢?答案是:效率高! Selenium 有两个版本,目前最新版本是 2.53.1(2016/3/22)

    Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾经是 Selenium 的竞争对手)。也就是说 Selenium 2 是 Selenium 和 WebDriver 两个项目的合并,即 Selenium 2 兼容 Selenium,它既支持 Selenium API 也支持 WebDriver API。

    更多详情可以查看 Webdriver 的简介。 Webdriver 嗯,通过以上描述,我们应该对 Selenium 有了大概对认识,接下来就让我们开始进入动态爬取的新世界吧。 本文参考内容来自 Selenium官网 SeleniumPython文档

    安装

    首先安装 Selenium

    1
    pip install selenium

    或者下载源码 下载源码 然后解压后运行下面的命令进行安装

    1
    python setup.py install

    安装好了之后我们便开始探索抓取方法了。

    快速开始

    初步体验

    我们先来一个小例子感受一下 Selenium,这里我们用 Chrome 浏览器来测试,方便查看效果,到真正爬取的时候换回 PhantomJS 即可。

    1
    2
    3
    4
    from selenium import webdriver

    browser = webdriver.Chrome()
    browser.get('http://www.baidu.com/')

    运行这段代码,会自动打开浏览器,然后访问百度。 如果程序执行错误,浏览器没有打开,那么应该是没有装 Chrome 浏览器或者 Chrome 驱动没有配置在环境变量里。下载驱动,然后将驱动文件路径配置在环境变量即可。 浏览器驱动下载 比如我的是 Mac OS,就把下载好的文件放在 /usr/bin 目录下就可以了。

    模拟提交

    下面的代码实现了模拟提交提交搜索的功能,首先等页面加载完成,然后输入到搜索框文本,点击提交。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys

    driver = webdriver.Chrome()
    driver.get("http://www.python.org")
    assert "Python" in driver.title
    elem = driver.find_element_by_name("q")
    elem.send_keys("pycon")
    elem.send_keys(Keys.RETURN)
    print driver.page_source

    同样是在 Chrome 里面测试,感受一下。

    The driver.get method will navigate to a page given by the URL. WebDriver will wait until the page has fully loaded (that is, the “onload” event has fired) before returning control to your test or script. It’s worth noting that if your page uses a lot of AJAX on load then WebDriver may not know when it has completely loaded.

    其中 driver.get 方法会打开请求的URL,WebDriver 会等待页面完全加载完成之后才会返回,即程序会等待页面的所有内容加载完成,JS渲染完毕之后才继续往下执行。注意:如果这里用到了特别多的 Ajax 的话,程序可能不知道是否已经完全加载完毕。

    WebDriver offers a number of ways to find elements using one of the findelement_by* methods. For example, the input text element can be located by its name attribute using find_element_by_name method

    WebDriver 提供了许多寻找网页元素的方法,譬如 findelement_by* 的方法。例如一个输入框可以通过 find_element_by_name 方法寻找 name 属性来确定。

    Next we are sending keys, this is similar to entering keys using your keyboard. Special keys can be send using Keys class imported from selenium.webdriver.common.keys

    然后我们输入来文本然后模拟点击了回车,就像我们敲击键盘一样。我们可以利用 Keys 这个类来模拟键盘输入。 最后最重要的一点 获取网页渲染后的源代码。 输出 page_source 属性即可。 这样,我们就可以做到网页的动态爬取了。

    测试用例

    有了以上特性,我们当然可以用来写测试样例了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import unittest
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys

    class PythonOrgSearch(unittest.TestCase):

    def setUp(self):
    self.driver = webdriver.Chrome()

    def test_search_in_python_org(self):
    driver = self.driver
    driver.get("http://www.python.org")
    self.assertIn("Python", driver.title)
    elem = driver.find_element_by_name("q")
    elem.send_keys("pycon")
    elem.send_keys(Keys.RETURN)
    assert "No results found." not in driver.page_source

    def tearDown(self):
    self.driver.close()

    if __name__ == "__main__":
    unittest.main()

    运行程序,同样的功能,我们将其封装为测试标准类的形式。

    The test case class is inherited from unittest.TestCase. Inheriting from TestCase class is the way to tell unittest module that this is a test case. The setUp is part of initialization, this method will get called before every test function which you are going to write in this test case class. The test case method should always start with characters test. The tearDown method will get called after every test method. This is a place to do all cleanup actions. You can also call quit method instead of close. The quit will exit the entire browser, whereas close will close a tab, but if it is the only tab opened, by default most browser will exit entirely.

    测试用例是继承了 unittest.TestCase 类,继承这个类表明这是一个测试类。setUp方法是初始化的方法,这个方法会在每个测试类中自动调用。每一个测试方法命名都有规范,必须以 test 开头,会自动执行。最后的 tearDown 方法会在每一个测试方法结束之后调用。这相当于最后的析构方法。在这个方法里写的是 close 方法,你还可以写 quit 方法。不过 close 方法相当于关闭了这个 TAB 选项卡,然而 quit 是退出了整个浏览器。当你只开启了一个 TAB 选项卡的时候,关闭的时候也会将整个浏览器关闭。

    页面操作

    页面交互

    仅仅抓取页面没有多大卵用,我们真正要做的是做到和页面交互,比如点击,输入等等。那么前提就是要找到页面中的元素。WebDriver提供了各种方法来寻找元素。例如下面有一个表单输入框。

    1
    <input type="text" name="passwd" id="passwd-id" />

    我们可以这样获取它

    1
    2
    3
    4
    element = driver.find_element_by_id("passwd-id")
    element = driver.find_element_by_name("passwd")
    element = driver.find_elements_by_tag_name("input")
    element = driver.find_element_by_xpath("//input[@id='passwd-id']")

    你还可以通过它的文本链接来获取,但是要小心,文本必须完全匹配才可以,所以这并不是一个很好的匹配方式。 而且你在用 xpath 的时候还需要注意的是,如果有多个元素匹配了 xpath,它只会返回第一个匹配的元素。如果没有找到,那么会抛出 NoSuchElementException 的异常。 获取了元素之后,下一步当然就是向文本输入内容了,可以利用下面的方法

    1
    element.send_keys("some text")

    同样你还可以利用 Keys 这个类来模拟点击某个按键。

    1
    element.send_keys("and some", Keys.ARROW_DOWN)

    你可以对任何获取到到元素使用 send_keys 方法,就像你在 GMail 里面点击发送键一样。不过这样会导致的结果就是输入的文本不会自动清除。所以输入的文本都会在原来的基础上继续输入。你可以用下面的方法来清除输入文本的内容。

    1
    element.clear()

    这样输入的文本会被清除。

    填充表单

    我们已经知道了怎样向文本框中输入文字,但是其它的表单元素呢?例如下拉选项卡的的处理可以如下

    1
    2
    3
    4
    5
    element = driver.find_element_by_xpath("//select[@name='name']")
    all_options = element.find_elements_by_tag_name("option")
    for option in all_options:
    print("Value is: %s" % option.get_attribute("value"))
    option.click()

    首先获取了第一个 select 元素,也就是下拉选项卡。然后轮流设置了 select 选项卡中的每一个 option 选项。你可以看到,这并不是一个非常有效的方法。 其实 WebDriver 中提供了一个叫 Select 的方法,可以帮助我们完成这些事情。

    1
    2
    3
    4
    5
    from selenium.webdriver.support.ui import Select
    select = Select(driver.find_element_by_name('name'))
    select.select_by_index(index)
    select.select_by_visible_text("text")
    select.select_by_value(value)

    如你所见,它可以根据索引来选择,可以根据值来选择,可以根据文字来选择。是十分方便的。 全部取消选择怎么办呢?很简单

    1
    2
    select = Select(driver.find_element_by_id('id'))
    select.deselect_all()

    这样便可以取消所有的选择。 另外我们还可以通过下面的方法获取所有的已选选项。

    1
    2
    select = Select(driver.find_element_by_xpath("xpath"))
    all_selected_options = select.all_selected_options

    获取所有可选选项是

    1
    options = select.options

    如果你把表单都填好了,最后肯定要提交表单对吧。怎吗提交呢?很简单

    1
    driver.find_element_by_id("submit").click()

    这样就相当于模拟点击了 submit 按钮,做到表单提交。 当然你也可以单独提交某个元素

    1
    element.submit()

    方法,WebDriver 会在表单中寻找它所在的表单,如果发现这个元素并没有被表单所包围,那么程序会抛出 NoSuchElementException 的异常。

    元素拖拽

    要完成元素的拖拽,首先你需要指定被拖动的元素和拖动目标元素,然后利用 ActionChains 类来实现。

    1
    2
    3
    4
    5
    6
    element = driver.find_element_by_name("source")
    target = driver.find_element_by_name("target")

    from selenium.webdriver import ActionChains
    action_chains = ActionChains(driver)
    action_chains.drag_and_drop(element, target).perform()

    这样就实现了元素从 source 拖动到 target 的操作。

    页面切换

    一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下

    1
    driver.switch_to_window("windowName")

    另外你可以使用 window_handles 方法来获取每个窗口的操作对象。例如

    1
    2
    for handle in driver.window_handles:
    driver.switch_to_window(handle)

    另外切换 frame 的方法如下

    1
    driver.switch_to_frame("frameName.0.child")

    这样焦点会切换到一个 name 为 child 的 frame 上。

    弹窗处理

    当你出发了某个事件之后,页面出现了弹窗提示,那么你怎样来处理这个提示或者获取提示信息呢?

    1
    alert = driver.switch_to_alert()

    通过上述方法可以获取弹窗对象。

    历史记录

    那么怎样来操作页面的前进和后退功能呢?

    1
    2
    driver.forward()
    driver.back()

    嗯,简洁明了。

    Cookies处理

    为页面添加 Cookies,用法如下

    1
    2
    3
    4
    5
    6
    # Go to the correct domain
    driver.get("http://www.example.com")

    # Now set the cookie. This one's valid for the entire domain
    cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
    driver.add_cookie(cookie)

    获取页面 Cookies,用法如下

    1
    2
    3
    4
    5
    # Go to the correct domain
    driver.get("http://www.example.com")

    # And now output all the available cookies for the current URL
    driver.get_cookies()

    以上便是 Cookies 的处理,同样是非常简单的。

    元素选取

    关于元素的选取,有如下的API 单个元素选取

    • find_element_by_id
    • find_element_by_name
    • find_element_by_xpath
    • find_element_by_link_text
    • find_element_by_partial_link_text
    • find_element_by_tag_name
    • find_element_by_class_name
    • find_element_by_css_selector

    多个元素选取

    • find_elements_by_name
    • find_elements_by_xpath
    • find_elements_by_link_text
    • find_elements_by_partial_link_text
    • find_elements_by_tag_name
    • find_elements_by_class_name
    • find_elements_by_css_selector

    另外还可以利用 By 类来确定哪种选择方式

    1
    2
    3
    4
    from selenium.webdriver.common.by import By

    driver.find_element(By.XPATH, '//button[text()="Some text"]')
    driver.find_elements(By.XPATH, '//button')

    By 类的一些属性如下

    1
    2
    3
    4
    5
    6
    7
    8
    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

    更详细的元素选择方法参见官方文档 元素选择

    页面等待

    这是非常重要的一部分,现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。这会让元素定位困难而且会提高产生 ElementNotVisibleException 的概率。 所以 Selenium 提供了两种等待方式,一种是隐式等待,一种是显式等待。 隐式等待是等待特定的时间,显式等待是指定某一条件直到这个条件成立时继续执行。

    显式等待

    显式等待指定某个条件,然后设置最长等待时间。如果在这个时间还没有找到元素,那么便会抛出异常了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    driver = webdriver.Chrome()
    driver.get("http://somedomain/url_that_delays_loading")
    try:
    element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
    finally:
    driver.quit()

    程序默认会 500ms 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。 下面是一些内置的等待条件,你可以直接调用这些条件,而不用自己写某些等待条件了。

    • title_is
    • title_contains
    • presence_of_element_located
    • visibility_of_element_located
    • visibility_of
    • presence_of_all_elements_located
    • text_to_be_present_in_element
    • text_to_be_present_in_element_value
    • frame_to_be_available_and_switch_to_it
    • invisibility_of_element_located
    • element_to_be_clickable - it is Displayed and Enabled.
    • staleness_of
    • element_to_be_selected
    • element_located_to_be_selected
    • element_selection_state_to_be
    • element_located_selection_state_to_be
    • alert_is_present
    1
    2
    3
    4
    from selenium.webdriver.support import expected_conditions as EC

    wait = WebDriverWait(driver, 10)
    element = wait.until(EC.element_to_be_clickable((By.ID,'someid')))

    隐式等待

    隐式等待比较简单,就是简单地设置一个等待时间,单位为秒。

    1
    2
    3
    4
    5
    6
    from selenium import webdriver

    driver = webdriver.Chrome()
    driver.implicitly_wait(10) # seconds
    driver.get("http://somedomain/url_that_delays_loading")
    myDynamicElement = driver.find_element_by_id("myDynamicElement")

    当然如果不设置,默认等待时间为0。

    程序框架

    对于页面测试和分析,官方提供了一个比较明晰的代码结构,可以参考。 页面测试架构

    API

    到最后,肯定是放松最全最重要的API了,比较多,希望大家可以多加练习。 API

    结语

    以上就是 Selenium 的基本用法,我们讲解了页面交互,页面渲染之后的源代码的获取。这样,即使页面是 JS 渲染而成的,我们也可以手到擒来了。就是这么溜!

    Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    如下为原文。

    前言

    大家有没有发现之前我们写的爬虫都有一个共性,就是只能爬取单纯的 html 代码,如果页面是 JS 渲染的该怎么办呢?如果我们单纯去分析一个个后台的请求,手动去摸索 JS 渲染的到的一些结果,那简直没天理了。所以,我们需要有一些好用的工具来帮助我们像浏览器一样渲染 JS 处理的页面。 其中有一个比较常用的工具,那就是 PhantomJS

    Full web stack No browser required

    PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast andnative support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

    PhantomJS 是一个无界面的,可脚本编程的 WebKit 浏览器引擎。它原生支持多种 web 标准:DOM 操作,CSS 选择器,JSON,Canvas 以及 SVG。 好,接下来我们就一起来了解一下这个神奇好用的库的用法吧。

    安装

    PhantomJS 安装方法有两种,一种是下载源码之后自己来编译,另一种是直接下载编译好的二进制文件。然而自己编译需要的时间太长,而且需要挺多的磁盘空间。官方推荐直接下载二进制文件然后安装。 大家可以依照自己的开发平台选择不同的包进行下载 下载地址 当然如果你不嫌麻烦,可以选择 下载源码 然后自己编译。 目前(2016/3/21)最新发行版本是 v2.1, 安装完成之后命令行输入

    1
    phantomjs -v

    如果正常显示版本号,那么证明安装成功了。如果提示错误,那么请重新安装。 本文介绍大部分内容来自于官方文档,博主对其进行了整理,学习更多请参考 官方文档

    快速开始

    第一个程序

    第一个程序当然是 Hello World,新建一个 js 文件。命名为 helloworld.js

    1
    2
    console.log('Hello, world!');
    phantom.exit();

    命令行输入

    1
    phantomjs helloworld.js

    程序输出了 Hello,world!程序第二句话终止了 phantom 的执行。 注意:phantom.exit();这句话非常重要,否则程序将永远不会终止。

    页面加载

    可以利用 phantom 来实现页面的加载,下面的例子实现了页面的加载并将页面保存为一张图片。

    1
    2
    3
    4
    5
    6
    7
    8
    var page = require('webpage').create();
    page.open('http://cuiqingcai.com', function (status) {
    console.log("Status: " + status);
    if (status === "success") {
    page.render('example.png');
    }
    phantom.exit();
    });

    首先创建了一个 webpage 对象,然后加载本站点主页,判断响应状态,如果成功,那么保存截图为 example.png 以上代码命名为 pageload.js,命令行

    1
    phantomjs pageload.js

    发现执行成功,然后目录下多了一张图片,example.png example 因为这个 render 方法,phantom 经常会用到网页截图的功能。

    测试页面加载速度

    下面这个例子计算了一个页面的加载速度,同时还用到了命令行传参的特性。新建文件保存为 loadspeed.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var page = require('webpage').create(),
    system = require('system'),
    t, address;

    if (system.args.length === 1) {
    console.log('Usage: loadspeed.js <some URL>');
    phantom.exit();
    }

    t = Date.now();
    address = system.args[1];
    page.open(address, function(status) {
    if (status !== 'success') {
    console.log('FAIL to load the address');
    } else {
    t = Date.now() - t;
    console.log('Loading ' + system.args[1]);
    console.log('Loading time ' + t + ' msec');
    }
    phantom.exit();
    });

    程序判断了参数的多少,如果参数不够,那么终止运行。然后记录了打开页面的时间,请求页面之后,再纪录当前时间,二者之差就是页面加载速度。

    1
    phantomjs loadspeed.js http://cuiqingcai.com

    运行结果

    1
    2
    Loading http://cuiqingcai.com
    Loading time 11678 msec

    这个时间包括 JS 渲染的时间,当然和网速也有关。

    代码评估

    To evaluate JavaScript code in the context of the web page, use evaluate() function. The execution is “sandboxed”, there is no way for the code to access any JavaScript objects and variables outside its own page context. An object can be returned from evaluate(), however it is limited to simple objects and can’t contain functions or closures.

    利用 evaluate 方法我们可以获取网页的源代码。这个执行是“沙盒式”的,它不会去执行网页外的 JavaScript 代码。evalute 方法可以返回一个对象,然而返回值仅限于对象,不能包含函数(或闭包)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var url = 'http://www.baidu.com';
    var page = require('webpage').create();
    page.open(url, function(status) {
    var title = page.evaluate(function() {
    return document.title;
    });
    console.log('Page title is ' + title);
    phantom.exit();
    });

    以上代码获取了百度的网站标题。

    1
    Page title is 百度一下,你就知道

    任何来自于网页并且包括来自 evaluate() 内部代码的控制台信息,默认不会显示。 需要重写这个行为,使用 onConsoleMessage 回调函数,示例可以改写成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var url = 'http://www.baidu.com';
    var page = require('webpage').create();
    page.onConsoleMessage = function (msg) {
    console.log(msg);
    };
    page.open(url, function (status) {
    page.evaluate(function () {
    console.log(document.title);
    });
    phantom.exit();
    });

    这样的话,如果你用浏览器打开百度首页,打开调试工具的 console,可以看到控制台输出信息。 重写了 onConsoleMessage 方法之后,可以发现控制台输出的结果和我们需要输出的标题都打印出来了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    一张网页,要经历怎样的过程,才能抵达用户面前?
    一位新人,要经历怎样的成长,才能站在技术之巅?
    探寻这里的秘密;
    体验这里的挑战;
    成为这里的主人;
    加入百度,加入网页搜索,你,可以影响世界。

    请将简历发送至 %c ps_recruiter@baidu.com( 邮件标题请以“姓名-应聘XX职位-来自console”命名) color:red
    职位介绍:http://dwz.cn/hr2013
    百度一下,你就知道

    啊,我没有在为百度打广告!

    屏幕捕获

    Since PhantomJS is using WebKit, a real layout and rendering engine, it can capture a web page as a screenshot. Because PhantomJS can render anything on the web page, it can be used to convert contents not only in HTML and CSS, but also SVG and Canvas.

    因为 PhantomJS 使用了 WebKit 内核,是一个真正的布局和渲染引擎,它可以像屏幕截图一样捕获一个 web 界面。因为它可以渲染网页中的人和元素,所以它不仅用到 HTML,CSS 的内容转化,还用在 SVG,Canvas。可见其功能是相当强大的。 下面的例子就捕获了 github 网页的截图。上文有类似内容,不再演示。

    1
    2
    3
    4
    5
    var page = require('webpage').create();
    page.open('http://github.com/', function() {
    page.render('github.png');
    phantom.exit();
    });

    除了 png 格式的转换,PhantomJS 还支持 jpg,gif,pdf 等格式。 测试样例 其中最重要的方法便是 viewportSize 和 clipRect 属性。 viewportSize 是视区的大小,你可以理解为你打开了一个浏览器,然后把浏览器窗口拖到了多大。 clipRect 是裁切矩形的大小,需要四个参数,前两个是基准点,后两个参数是宽高。 通过下面的小例子感受一下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var page = require('webpage').create();
    //viewportSize being the actual size of the headless browser
    page.viewportSize = { width: 1024, height: 768 };
    //the clipRect is the portion of the page you are taking a screenshot of
    page.clipRect = { top: 0, left: 0, width: 1024, height: 768 };
    //the rest of the code is the same as the previous example
    page.open('http://cuiqingcai.com/', function() {
    page.render('germy.png');
    phantom.exit();
    });

    运行结果 germy 就相当于把浏览器窗口拖到了 1024x768 大小,然后从左上角裁切出了 1024x768 的页面。

    网络监听

    Because PhantomJS permits the inspection of network traffic, it is suitable to build various analysis on the network behavior and performance.

    因为 PhantomJS 有网络通信的检查功能,它也很适合用来做网络行为的分析。

    When a page requests a resource from a remote server, both the request and the response can be tracked via onResourceRequested and onResourceReceived callback.

    当接受到请求时,可以通过改写 onResourceRequested 和 onResourceReceived 回调函数来实现接收到资源请求和资源接受完毕的监听。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var url = 'http://www.cuiqingcai.com';
    var page = require('webpage').create();
    page.onResourceRequested = function(request) {
    console.log('Request ' + JSON.stringify(request, undefined, 4));
    };
    page.onResourceReceived = function(response) {
    console.log('Receive ' + JSON.stringify(response, undefined, 4));
    };
    page.open(url);

    运行结果会打印出所有资源的请求和接收状态,以 JSON 格式输出。

    页面自动化处理

    Because PhantomJS can load and manipulate a web page, it is perfect to carry out various page automations.

    因为 PhantomJS 可以加载和操作一个 web 页面,所以用来自动化处理也是非常适合的。

    DOM 操作

    Since the script is executed as if it is running on a web browser, standard DOM scripting and CSS selectors work just fine.

    脚本都是像在浏览器中运行的,所以标准的 JavaScript 的 DOM 操作和 CSS 选择器也是生效的。 例如下面的例子就修改了 User-Agent,然后还返回了页面中某元素的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var page = require('webpage').create();
    console.log('The default user agent is ' + page.settings.userAgent);
    page.settings.userAgent = 'SpecialAgent';
    page.open('http://www.httpuseragent.org', function(status) {
    if (status !== 'success') {
    console.log('Unable to access network');
    } else {
    var ua = page.evaluate(function() {
    return document.getElementById('myagent').textContent;
    });
    console.log(ua);
    }
    phantom.exit();
    });

    运行结果

    1
    2
    The default user agent is Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.0 Safari/538.1
    Your Http User Agent string is: SpecialAgent

    首先打印出了默认的 User-Agent,然后通过修改它,请求验证 User-Agent 的一个站点,通过选择器得到了修改后的 User-Agent。

    使用附加库

    在 1.6 版本之后允许添加外部的 JS 库,比如下面的例子添加了 jQuery,然后执行了 jQuery 代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var page = require('webpage').create();
    page.open('http://www.sample.com', function() {
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {
    page.evaluate(function() {
    $("button").click();
    });
    phantom.exit()
    });
    });

    引用了 jQuery 之后,我们便可以在下面写一些 jQuery 代码了。

    Webpage 对象

    在前面我们介绍了 webpage 对象的几个方法和属性,其实它本身还有其它很多的属性。具体的内容可以参考 Webpage Webpage 用例 里面介绍了 webpage 的所有属性,方法,回调。

    命令行

    Command-line Options PhantomJS 提供的命令行选项有:

    --help or -h lists all possible command-line options. Halts immediately, will not run a script passed as argument. [帮助列表] —version or -v prints out the version of PhantomJS. Halts immediately, will not run a script passed as argument. [查看版本] —cookies-file=/path/to/cookies.txt specifies the file name to store the persistent Cookies. [指定存放 cookies 的路径] —disk-cache=[true|false] enables disk cache (at desktop services cache storage location, default is false). Also accepted: [yes|no]. [硬盘缓存开关,默认为关] —ignore-ssl-errors=[true|false] ignores SSL errors, such as expired or self-signed certificate errors (default is false). Also accepted: [yes|no]. [忽略 ssl 错误,默认不忽略] —load-images=[true|false] load all inlined images (default is true). Also accepted: [yes|no]. [加载图片,默认为加载] —local-storage-path=/some/path path to save LocalStorage content and WebSQL content. [本地存储路径,如本地文件和 SQL 文件等] —local-storage-quota=number maximum size to allow for data. [本地文件最大大小] —local-to-remote-url-access=[true|false] allows local content to access remote URL (default is false). Also accepted: [yes|no]. [是否允许远程加载文件,默认不允许] —max-disk-cache-size=size limits the size of disk cache (in KB). [最大缓存空间] —output-encoding=encoding sets the encoding used for terminal output (default is utf8). [默认输出编码,默认 utf8] —remote-debugger-port starts the script in a debug harness and listens on the specified port [远程调试端口] —remote-debugger-autorun runs the script in the debugger immediately: ‘yes’ or ‘no’ (default) [在调试环境下是否立即执行脚本,默认否] —proxy=address:port specifies the proxy server to use (e.g. —proxy=192.168.1.42:8080). [代理] —proxy-type=[http|socks5|none] specifies the type of the proxy server (default is http). [代理类型,默认 http] —proxy-auth specifies the authentication information for the proxy, e.g. —proxy-auth=username:password). [代理认证] —script-encoding=encoding sets the encoding used for the starting script (default is utf8). [脚本编码,默认 utf8] —ssl-protocol=[sslv3|sslv2|tlsv1|any’] sets the SSL protocol for secure connections (default is SSLv3). [SSL 协议,默认 SSLv3] —ssl-certificates-path= Sets the location for custom CA certificates (if none set, uses system default). [SSL 证书路径,默认系统默认路径] —web-security=[true|false] enables web security and forbids cross-domain XHR (default is true). Also accepted: [yes|no]. [是否开启安全保护和禁止异站 Ajax,默认开启保护] —webdriver starts in ‘Remote WebDriver mode’ (embedded GhostDriver): ‘[[:]]’ (default ‘127.0.0.1:8910’) [以远程 WebDriver 模式启动] —webdriver-selenium-grid-hub URL to the Selenium Grid HUB: ‘URLTOHUB’ (default ‘none’) (NOTE: works only together with ‘—webdriver’) [Selenium 接口] —config=/path/to/config.json can utilize a JavaScript Object Notation (JSON) configuration file instead of passing in multiple command-line optionss [所有的命令行配置从 config.json 中读取]

    注:JSON 文件配置格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    /* Same as: --ignore-ssl-errors=true */
    "ignoreSslErrors": true,

    /* Same as: --max-disk-cache-size=1000 */
    "maxDiskCacheSize": 1000,

    /* Same as: --output-encoding=utf8 */
    "outputEncoding": "utf8"

    /* etc. */
    }

    There are some keys that do not translate directly:

    * --disk-cache => diskCacheEnabled
    * --load-images => autoLoadImages
    * --local-storage-path => offlineStoragePath
    * --local-storage-quota => offlineStorageDefaultQuota
    * --local-to-remote-url-access => localToRemoteUrlAccessEnabled
    * --web-security => webSecurityEnabled

    以上是命令行的基本配置

    实例

    在此提供官方文档实例,多对照实例练习,使用起来会更得心应手。 官方实例

    结语

    以上是博主对 PhantomJS 官方文档的基本总结和翻译,如有差错,希望大家可以指正。另外可能有的小伙伴觉得这个工具和 Python 有什么关系?不要急,后面会有 Python 和 PhantomJS 的综合使用的。

    Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    如下为原文。

    前言

    之前我们用了 urllib 库,这个作为入门的工具还是不错的,对了解一些爬虫的基本理念,掌握爬虫爬取的流程有所帮助。入门之后,我们就需要学习一些更加高级的内容和工具来方便我们的爬取。那么这一节来简单介绍一下 requests 库的基本用法。 注:Python 版本依然基于 2.7

    官方文档

    以下内容大多来自于官方文档,本文进行了一些修改和总结。要了解更多可以参考 官方文档

    安装

    利用 pip 安装

    1
    $ pip install requests

    或者利用 easy_install

    1
    $ easy_install requests

    通过以上两种方法均可以完成安装。

    引入

    首先我们引入一个小例子来感受一下

    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    r = requests.get('http://cuiqingcai.com')
    print type(r)
    print r.status_code
    print r.encoding
    #print r.text
    print r.cookies

    以上代码我们请求了本站点的网址,然后打印出了返回结果的类型,状态码,编码方式,Cookies等内容。 运行结果如下

    1
    2
    3
    4
    <class 'requests.models.Response'>
    200
    UTF-8
    <RequestsCookieJar[]>

    怎样,是不是很方便。别急,更方便的在后面呢。

    基本请求

    requests库提供了http所有的基本请求方式。例如

    1
    2
    3
    4
    5
    r = requests.post("http://httpbin.org/post")
    r = requests.put("http://httpbin.org/put")
    r = requests.delete("http://httpbin.org/delete")
    r = requests.head("http://httpbin.org/get")
    r = requests.options("http://httpbin.org/get")

    嗯,一句话搞定。

    基本GET请求

    最基本的GET请求可以直接用get方法

    1
    r = requests.get("http://httpbin.org/get")

    如果想要加参数,可以利用 params 参数

    1
    2
    3
    4
    5
    import requests

    payload = {'key1': 'value1', 'key2': 'value2'}
    r = requests.get("http://httpbin.org/get", params=payload)
    print r.url

    运行结果

    1
    http://httpbin.org/get?key2=value2&key1=value1

    如果想请求JSON文件,可以利用 json() 方法解析 例如自己写一个JSON文件命名为a.json,内容如下

    1
    2
    3
    ["foo", "bar", {
    "foo": "bar"
    }]

    利用如下程序请求并解析

    1
    2
    3
    4
    5
    import requests

    r = requests.get("a.json")
    print r.text
    print r.json()

    运行结果如下,其中一个是直接输出内容,另外一个方法是利用 json() 方法解析,感受下它们的不同

    1
    2
    3
    4
    ["foo", "bar", {
    "foo": "bar"
    }]
    [u'foo', u'bar', {u'foo': u'bar'}]

    如果想获取来自服务器的原始套接字响应,可以取得 r.raw 。 不过需要在初始请求中设置 stream=True 。

    1
    2
    3
    4
    5
    r = requests.get('https://github.com/timeline.json', stream=True)
    r.raw
    <requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
    r.raw.read(10)
    '\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

    这样就获取了网页原始套接字内容。 如果想添加 headers,可以传 headers 参数

    1
    2
    3
    4
    5
    6
    import requests

    payload = {'key1': 'value1', 'key2': 'value2'}
    headers = {'content-type': 'application/json'}
    r = requests.get("http://httpbin.org/get", params=payload, headers=headers)
    print r.url

    通过headers参数可以增加请求头中的headers信息

    基本POST请求

    对于 POST 请求来说,我们一般需要为它增加一些参数。那么最基本的传参方法可以利用 data 这个参数。

    1
    2
    3
    4
    5
    import requests

    payload = {'key1': 'value1', 'key2': 'value2'}
    r = requests.post("http://httpbin.org/post", data=payload)
    print r.text

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "args": {},
    "data": "",
    "files": {},
    "form": {
    "key1": "value1",
    "key2": "value2"
    },
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "23",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    },
    "json": null,
    "url": "http://httpbin.org/post"
    }

    可以看到参数传成功了,然后服务器返回了我们传的数据。 有时候我们需要传送的信息不是表单形式的,需要我们传JSON格式的数据过去,所以我们可以用 json.dumps() 方法把表单数据序列化。

    1
    2
    3
    4
    5
    6
    7
    import json
    import requests

    url = 'http://httpbin.org/post'
    payload = {'some': 'data'}
    r = requests.post(url, data=json.dumps(payload))
    print r.text

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "args": {},
    "data": "{\"some\": \"data\"}",
    "files": {},
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "16",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    },
    "json": {
    "some": "data"
    },
    "url": "http://httpbin.org/post"
    }

    通过上述方法,我们可以POST JSON格式的数据 如果想要上传文件,那么直接用 file 参数即可 新建一个 a.txt 的文件,内容写上 Hello World!

    1
    2
    3
    4
    5
    6
    import requests

    url = 'http://httpbin.org/post'
    files = {'file': open('test.txt', 'rb')}
    r = requests.post(url, files=files)
    print r.text

    可以看到运行结果如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    "args": {},
    "data": "",
    "files": {
    "file": "Hello World!"
    },
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "156",
    "Content-Type": "multipart/form-data; boundary=7d8eb5ff99a04c11bb3e862ce78d7000",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    },
    "json": null,
    "url": "http://httpbin.org/post"
    }

    这样我们便成功完成了一个文件的上传。 requests 是支持流式上传的,这允许你发送大的数据流或文件而无需先把它们读入内存。要使用流式上传,仅需为你的请求体提供一个类文件对象即可

    1
    2
    with open('massive-body') as f:
    requests.post('http://some.url/streamed', data=f)

    这是一个非常实用方便的功能。

    Cookies

    如果一个响应中包含了cookie,那么我们可以利用 cookies 变量来拿到

    1
    2
    3
    4
    5
    6
    import requests

    url = 'http://example.com'
    r = requests.get(url)
    print r.cookies
    print r.cookies['example_cookie_name']

    以上程序仅是样例,可以用 cookies 变量来得到站点的 cookies 另外可以利用 cookies 变量来向服务器发送 cookies 信息

    1
    2
    3
    4
    5
    6
    import requests

    url = 'http://httpbin.org/cookies'
    cookies = dict(cookies_are='working')
    r = requests.get(url, cookies=cookies)
    print r.text

    运行结果

    1
    '{"cookies": {"cookies_are": "working"}}'

    可以已经成功向服务器发送了 cookies

    超时配置

    可以利用 timeout 变量来配置最大请求时间

    1
    requests.get('http://github.com', timeout=0.001)

    注:timeout 仅对连接过程有效,与响应体的下载无关。 也就是说,这个时间只限制请求的时间。即使返回的 response 包含很大内容,下载需要一定时间,然而这并没有什么卵用。

    会话对象

    在以上的请求中,每次请求其实都相当于发起了一个新的请求。也就是相当于我们每个请求都用了不同的浏览器单独打开的效果。也就是它并不是指的一个会话,即使请求的是同一个网址。比如

    1
    2
    3
    4
    5
    import requests

    requests.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    r = requests.get("http://httpbin.org/cookies")
    print(r.text)

    结果是

    1
    2
    3
    {
    "cookies": {}
    }

    很明显,这不在一个会话中,无法获取 cookies,那么在一些站点中,我们需要保持一个持久的会话怎么办呢?就像用一个浏览器逛淘宝一样,在不同的选项卡之间跳转,这样其实就是建立了一个长久会话。 解决方案如下

    1
    2
    3
    4
    5
    6
    import requests

    s = requests.Session()
    s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    r = s.get("http://httpbin.org/cookies")
    print(r.text)

    在这里我们请求了两次,一次是设置 cookies,一次是获得 cookies 运行结果

    1
    2
    3
    4
    5
    {
    "cookies": {
    "sessioncookie": "123456789"
    }
    }

    发现可以成功获取到 cookies 了,这就是建立一个会话到作用。体会一下。 那么既然会话是一个全局的变量,那么我们肯定可以用来全局的配置了。

    1
    2
    3
    4
    5
    6
    import requests

    s = requests.Session()
    s.headers.update({'x-test': 'true'})
    r = s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
    print r.text

    通过 s.headers.update 方法设置了 headers 的变量。然后我们又在请求中设置了一个 headers,那么会出现什么结果? 很简单,两个变量都传送过去了。 运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1",
    "X-Test": "true",
    "X-Test2": "true"
    }
    }

    如果get方法传的headers 同样也是 x-test 呢?

    1
    r = s.get('http://httpbin.org/headers', headers={'x-test': 'true'})

    嗯,它会覆盖掉全局的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1",
    "X-Test": "true"
    }
    }

    那如果不想要全局配置中的一个变量了呢?很简单,设置为 None 即可

    1
    r = s.get('http://httpbin.org/headers', headers={'x-test': None})

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    }
    }

    嗯,以上就是 session 会话的基本用法

    SSL证书验证

    现在随处可见 https 开头的网站,Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样。要想检查某个主机的SSL证书,你可以使用 verify 参数 现在 12306 证书不是无效的嘛,来测试一下

    1
    2
    3
    4
    import requests

    r = requests.get('https://kyfw.12306.cn/otn/', verify=True)
    print r.text

    结果

    1
    requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)

    果真如此 来试下 github 的

    1
    2
    3
    4
    import requests

    r = requests.get('https://github.com', verify=True)
    print r.text

    嗯,正常请求,内容我就不输出了。 如果我们想跳过刚才 12306 的证书验证,把 verify 设置为 False 即可

    1
    2
    3
    4
    import requests

    r = requests.get('https://kyfw.12306.cn/otn/', verify=False)
    print r.text

    发现就可以正常请求了。在默认情况下 verify 是 True,所以如果需要的话,需要手动设置下这个变量。

    代理

    如果需要使用代理,你可以通过为任意请求方法提供 proxies 参数来配置单个请求

    1
    2
    3
    4
    5
    6
    7
    import requests

    proxies = {
    "https": "http://41.118.132.69:4433"
    }
    r = requests.post("http://httpbin.org/post", proxies=proxies)
    print r.text

    也可以通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来配置代理

    1
    2
    export HTTP_PROXY="http://10.10.1.10:3128"
    export HTTPS_PROXY="http://10.10.1.10:1080"

    通过以上方式,可以方便地设置代理。

    API

    以上讲解了 requests 中最常用的参数,如果需要用到更多,请参考官方文档 API API

    结语

    以上总结了一下 requests 的基本用法,如果你对爬虫有了一定的基础,那么肯定可以很快上手,在此就不多赘述了。 练习才是王道,大家尽快投注于实践中吧。

    JavaScript

    前言

    之前在用jQuery,不过有时候用着用着一些用法发现并没有用到过,比较陌生,现在重新梳理一下,把易忽略的知识点总结一下,长期更新。 参考梳理来源: 慕课网

    sele1,sele2,seleN选择器

    有时需要精确的选择任意多个指定的元素,类似于从文具盒中挑选出多根自已喜欢的笔,就需要调用sele1,sele2,seleN选择器,它的调用格式如下: $(“sele1,sele2,seleN”) 其中参数sele1、sele2到seleN为有效选择器,每个选择器之间用“,”号隔开,它们可以是之前提及的各种类型选择器,如$(“#id”)、$(“.class”)、$(“selector”)选择器等。 例如,通过选择器获取其中的任意两个元素,并将它们显示的内容设为相同,如图所示: 在浏览器中显示的效果: 虽然页面中添加了三个元素,但是通过使用$(“div,p”)选择器方式获取了其中的

    元素,并设置它们显示的内容。

    prev + next选择器

    俗话说“远亲不如近邻”,而通过prev + next选择器就可以查找与“prev”元素紧邻的下一个“next”元素,格式如下: $(“prev + next”) 其中参数prev为任何有效的选择器,参数“next”为另外一个有效选择器,它们之间的“+”表示一种上下的层次关系,也就是说,“prev”元素最紧邻的下一个元素由“next”选择器返回的并且只返回唯的一个元素。 例如,使用prev + next选择器,获取

    元素最近邻的下一个元素,如下图所示: 在浏览器中显示的效果:

    prev ~ siblings选择器

    与上一节中介绍的prev + next层次选择器相同,prev ~ siblings选择器也是查找prev 元素之后的相邻元素,但前者只获取第一个相邻的元素,而后者则获取prev 元素后面全部相邻的元素,它的调用格式如下: $(“prev ~ siblings”) 其中参数prev与siblings两者之间通过“~”符号形成一种层次相邻的关系,表明siblings选择器获取的元素都是prev元素之后的同辈元素。 例如,使用prev ~ next选择器,获取

    元素后面相邻的全部元素,并设置它们在页面中显示的内容,如下图所示: 在浏览器中显示的效果: 可以看出,调用$("p~span")选择器代码,获取了

    元素下面两个(全部)的元素,该元素不包含

    元素上面的元素和不属于同辈范围的元素。

    :contains(text)过滤选择器

    与上一节介绍的:eq(index)选择器按索引查找元素相比,有时候我们可能希望按照文本内容来查找一个或多个元素,那么使用:contains(text)选择器会更加方便, 它的功能是选择包含指定字符串的全部元素,它通常与其他元素结合使用,获取包含“text”字符串内容的全部元素对象。其中参数text表示页面中的文字。 例如: 在浏览器中显示的效果: 从图中可以看出,调用li:contains('土豪')代码,可以很方便地获取

  • 包含‘土豪’字符内容的全部元素,并且只要与选择的元素中或子元素中包含该字符内容,就可以被选中。 注意:li:contains('土豪') 土豪为什么必须加单引号呢?因为它是一个字符串,而不是一个变量,所以不加单或双引号的话是会报错的。

    :has(selector)过滤选择器

    除了在上一小节介绍的使用包含的字符串内容过滤元素之外,还可以使用包含的元素名称来过滤,:has(selector)过滤选择器的功能是获取选择器中包含指定元素名称的全部元素,其中selector参数就是包含的元素名称,是被包含元素。 例如:获取指定包含某个元素名的全部

  • 元素,并改变它们显示文字的颜色,如下图所示: 在浏览器中显示的效果: 可以看出,通过使用$("li:has('p')")选择器代码,获取了包含

    元素的全部

  • 元素,并通过css方法改变了这些元素在页面中显示的文字样式。

    :hidden过滤选择器

    :hidden过滤选择器的功能是获取全部不可见的元素,这些不可见的元素中包括type属性值为hidden的元素。 例如,调用:hidden选择器获取不可见的

    元素,并将该元素的内容显示在

    元素中,如下图所示: 在浏览器中显示的效果: 从图中可以看出,先调用$("p:hidden")代码获取隐藏的

    元素,并调用该元素的html()方法获取该元素中的内容,最后将该内容显示在

    元素中。

    :visible过滤选择器

    与上一节的:hidden过滤选择器相反,:visible过滤选择器获取的是全部可见的元素,也就是说,只要不将元素的display属性值设置为“none”,那么,都可以通过该选择器获取。 例如,使用:visible选择器获取可见的

    元素,并将该元素的内容显示在

    元素中,如下图所示: 在浏览器中显示的效果: 从图中可以看出,调用$("p:visible")选择器代码,获取那个可见的

    元素,并调用html()方法获取该元素的内容,最后将该内容显示在

    元素中。

    :input表单选择器

    如何获取表单全部元素?:input表单选择器可以实现,它的功能是返回全部的表单元素,不仅包括所有标记的表单元素,而且还包括