python——BeautifulSoup4解析器,JSON与JsonPATH,多线程爬虫,动态HTML处理
爬蟲的自我修養(yǎng)_3
一、CSS 選擇器:BeautifulSoup4
和 lxml 一樣,Beautiful Soup 也是一個(gè)HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 數(shù)據(jù)。
lxml 只會(huì)局部遍歷,而Beautiful Soup 是基于HTML DOM的,會(huì)載入整個(gè)文檔,解析整個(gè)DOM樹,因此時(shí)間和內(nèi)存開銷都會(huì)大很多,所以性能要低于lxml。
BeautifulSoup 用來解析 HTML 比較簡(jiǎn)單,API非常人性化,支持CSS選擇器、Python標(biāo)準(zhǔn)庫中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已經(jīng)停止開發(fā),推薦現(xiàn)在的項(xiàng)目使用Beautiful Soup 4。使用 pip 安裝即可:
pip install beautifulsoup4官方文檔:http://beautifulsoup.readthedocs.io/zh_CN/v4.4.0
四大對(duì)象種類
Beautiful Soup將復(fù)雜HTML文檔轉(zhuǎn)換成一個(gè)復(fù)雜的樹形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都是Python對(duì)象,所有對(duì)象可以歸納為4種:
Tag
NavigableString
BeautifulSoup
Comment
1. Tag
Tag 通俗點(diǎn)講就是 HTML 中的一個(gè)個(gè)標(biāo)簽,例如:
<head><title>The Dormouse's story</title></head> <a class="sister" id="link1"><!-- Elsie --></a> <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
上面的titleheadap等等 HTML 標(biāo)簽加上里面包括的內(nèi)容就是 Tag,那么試著使用 Beautiful Soup 來獲取 Tags:
from bs4 import BeautifulSoup
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" id="link1"><!-- Elsie --></a>,
<a class="sister" id="link2">Lacie</a> and
<a class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
#創(chuàng)建 Beautiful Soup 對(duì)象
soup = BeautifulSoup(html)
print(soup.title)
# <title>The Dormouse's story</title>
print(soup.head)
# <head><title>The Dormouse's story</title></head>
print(soup.a) # 只能取出第一個(gè)a標(biāo)簽
# <a class="sister" id="link1"><!-- Elsie --></a>
print(soup.p)
# <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
print(type(soup.p)) # 類型
# <class 'bs4.element.Tag'>
# 取出所有的a標(biāo)簽并獲取他們的屬性,注意用的是i["..."]
for i in soup.find_all('a'):
print(i)
print(i['id'])
print(i['class'])
print(i['href'])
# <a class="sister" id="link1"
# name="6123">123</a>
# link1
# ['sister']
# http://example.com/elsie
對(duì)于 Tag,它有兩個(gè)重要的屬性,是 name 和 attrs
print(soup.name)
# [document] #soup 對(duì)象本身比較特殊,它的 name 即為 [document]
print(soup.head.name)
# head 對(duì)于其他內(nèi)部標(biāo)簽,輸出的值便為標(biāo)簽本身的名稱
# 標(biāo)簽內(nèi)部的name是算在attrs中的
# {'name': '我是a標(biāo)簽', 'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}
print(soup.p.attrs)
# {'class': ['title'], 'name': 'dromouse'}
# 在這里,我們把 p 標(biāo)簽的所有屬性打印輸出了出來,得到的類型是一個(gè)字典。
print(soup.p['class'] # soup.p.get('class'))
# ['title'] #還可以利用get方法,傳入屬性的名稱,二者是等價(jià)的
soup.p['class'] = "newClass"
print(soup.p) # 可以對(duì)這些屬性和內(nèi)容等等進(jìn)行修改
# <p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>
del soup.p['class'] # 還可以對(duì)這個(gè)屬性進(jìn)行刪除
print(soup.p)
# <p name="dromouse"><b>The Dormouse's story</b></p>
2. NavigableString
使用 .string 方法獲取標(biāo)簽中的內(nèi)容
print(soup.p.string) # The Dormouse's story print(type(soup.p.string)) # <class 'bs4.element.NavigableString'>
3. BeautifulSoup
BeautifulSoup 對(duì)象表示的是一個(gè)文檔的內(nèi)容。大部分時(shí)候,可以把它當(dāng)作 Tag 對(duì)象,是一個(gè)特殊的 Tag,我們可以分別獲取它的類型,名稱。
soup = BeautifulSoup(html,'lxml') # BeautifulSoup對(duì)象
print(type(soup.name))
# <type 'unicode'>
print(soup.name)
# [document]
print(soup.attrs) # 文檔本身的屬性為空
# {}
4. Comment
Comment 對(duì)象是一個(gè)特殊類型的 NavigableString 對(duì)象,其輸出的內(nèi)容不包括注釋符號(hào)。
print(soup.a) # <a class="sister" id="link1"><!-- Elsie --></a> print(soup.a.string) # Elsie ,在輸出的時(shí)候把<-!->注釋符號(hào)去掉了 print(type(soup.a.string)) # <class 'bs4.element.Comment'>
遍歷文檔樹
1. 直接子節(jié)點(diǎn) :.contents.children屬性
.content
tag 的 .content 屬性可以將tag的子節(jié)點(diǎn)以列表的方式輸出(把當(dāng)前標(biāo)簽下的所有標(biāo)簽以列表的方式輸出)
print soup.body.contents # 也可以直接遍歷HTML文檔
# [<p class="title" name="dromouse"><b>The Dormouse's story</b></p>, '
', <p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" id="link1" name="6123">123</a>,
<a class="sister" id="link2">Lacie</a> and
<a class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>, '
', <p class="story">...</p>, '
']
輸出方式為列表,我們可以用列表索引來獲取它的某一個(gè)元素
print soup.head.contents[0] # <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
.children
它返回的不是一個(gè) list,不過我們可以通過遍歷獲取所有子節(jié)點(diǎn)。
我們打印輸出 .children 看一下,可以發(fā)現(xiàn)它是一個(gè) list 生成器對(duì)象
print(soup.head.children)
#<listiterator object at 0x7f71457f5710>
for child in soup.body.children: # 也可以直接遍歷整個(gè)HTML文檔
print(child)
結(jié)果
<p class="title" name="dromouse"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a class="sister" id="link1"><!-- Elsie --></a>, <a class="sister" id="link2">Lacie</a> and <a class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p>
2. 所有子孫節(jié)點(diǎn):.descendants屬性
.contents 和 .children 屬性僅包含tag的直接子節(jié)點(diǎn),.descendants 屬性可以對(duì)所有tag的子孫節(jié)點(diǎn)進(jìn)行遞歸循環(huán),和 children類似(也是list生成器),我們也需要遍歷獲取其中的內(nèi)容。
html = """
<html>
<head>
<title>The Dormouse's story<a>sadcx</a></title>
</head>
"""
bs = BeautifulSoup(html,'lxml')
for child in bs.head.descendants: # 也可以直接遍歷整個(gè)HTML文檔
print(child)
結(jié)果
<title>The Dormouse's story<a>sadcx</a></title> The Dormouse's story <a>sadcx</a> sadcx
搜索文檔樹
1.find_all(name, attrs, recursive, text, **kwargs)
1)name 參數(shù)
name 參數(shù)可以查找所有名字為 name 的tag,字符串對(duì)象會(huì)被自動(dòng)忽略掉
A.傳字符串
最簡(jiǎn)單的過濾器是字符串.在搜索方法中傳入一個(gè)字符串參數(shù),Beautiful Soup會(huì)查找與字符串完整匹配的內(nèi)容,下面的例子用于查找文檔中所有的<b>標(biāo)簽:
print(soup.find_all('b'))
# [<b>The Dormouse's story</b>]
print(soup.find_all('a'))
#[<a class="sister" id="link1"><!-- Elsie --></a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]
B.傳正則表達(dá)式
如果傳入正則表達(dá)式作為參數(shù),Beautiful Soup會(huì)通過正則表達(dá)式的 match() 來匹配內(nèi)容.下面例子中找出所有以b開頭的標(biāo)簽,這表示<body>和<b>標(biāo)簽都應(yīng)該被找到
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
C.傳列表
如果傳入列表參數(shù),Beautiful Soup會(huì)將與列表中任一元素匹配的內(nèi)容返回.下面代碼找到文檔中所有<a>標(biāo)簽和<b>標(biāo)簽:
soup.find_all(["a", "b"]) # [<b>The Dormouse's story</b>, # <a class="sister" id="link1">Elsie</a>, # <a class="sister" id="link2">Lacie</a>, # <a class="sister" id="link3">Tillie</a>]
2)keyword 參數(shù)(屬性)
soup.find_all(id='link2')
# [<a class="sister" id="link2">Lacie</a>]
soup.find_all(input,attrs={"name":"_xsrf","class":"c1","id":"link2"})
# 屬性也可以以字典的格式傳
3)text 參數(shù)
通過 text 參數(shù)可以搜搜文檔中的字符串內(nèi)容,與 name 參數(shù)的可選值一樣, text 參數(shù)接受 字符串 , 正則表達(dá)式 , 列表
soup.find_all(text="Elsie")
# [u'Elsie']
soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']
soup.find_all(text=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]
2.find(name, attrs, recursive, text, **kwargs)
find方法和find_all的使用方法是一樣的,只不過find只找一個(gè)值,find_all返回的是一個(gè)列表
CSS選擇器
這就是另一種與 find_all 方法有異曲同工之妙的查找方法.
寫 CSS 時(shí),標(biāo)簽名不加任何修飾,類名前加.,id名前加#
在這里我們也可以利用類似的方法來篩選元素,用到的方法是soup.select(),返回類型是list
(1)通過標(biāo)簽名查找
print soup.select('title')
#[<title>The Dormouse's story</title>]
print soup.select('a')
#[<a class="sister" id="link1"><!-- Elsie --></a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]
print soup.select('b')
#[<b>The Dormouse's story</b>]
(2)通過類名查找
print soup.select('.sister')
#[<a class="sister" id="link1"><!-- Elsie --></a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]
(3)通過 id 名查找
print soup.select('#link1')
#[<a class="sister" id="link1"><!-- Elsie --></a>]
(4)組合查找
組合查找即和寫 class 文件時(shí),標(biāo)簽名與類名、id名進(jìn)行的組合原理是一樣的,例如查找 p 標(biāo)簽中,id 等于 link1的內(nèi)容,二者需要用空格分開
print soup.select('p #link1')
#[<a class="sister" id="link1"><!-- Elsie --></a>]
直接子標(biāo)簽查找,則使用>分隔
print soup.select("head > title")
#[<title>The Dormouse's story</title>]
(5)屬性查找
查找時(shí)還可以加入屬性元素,屬性需要用中括號(hào)括起來,注意屬性和標(biāo)簽屬于同一節(jié)點(diǎn),所以中間不能加空格,否則會(huì)無法匹配到。
print soup.select('a[class="sister"]')
#[<a class="sister" id="link1"><!-- Elsie --></a>, <a class="sister" id="link2">Lacie</a>, <a class="sister" id="link3">Tillie</a>]
print soup.select('a[)
#[<a class="sister" id="link1"><!-- Elsie --></a>]
同樣,屬性仍然可以與上述查找方式組合,不在同一節(jié)點(diǎn)的空格隔開,同一節(jié)點(diǎn)的不加空格
print soup.select('p a[)
#[<a class="sister" id="link1"><!-- Elsie --></a>]
(6) 獲取內(nèi)容
以上的 select 方法返回的結(jié)果都是列表形式,可以遍歷形式輸出,然后用 get_text() 方法來獲取它的內(nèi)容。
soup = BeautifulSoup(html, 'lxml')
print type(soup.select('title'))
print soup.select('title')[0].get_text()
for title in soup.select('title'):
print title.get_text()
1 from bs4 import BeautifulSoup
2 import requests,time
3
4 # 請(qǐng)求報(bào)頭
5 headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"}
6
7 def captcha(captcha_data):
8 """
9 處理驗(yàn)證碼
10 :return:
11 """
12 with open("captcha.jpg",'wb')as f:
13 f.write(captcha_data)
14 text = input("請(qǐng)輸入驗(yàn)證碼:")
15 return text # 返回用戶輸入的驗(yàn)證碼
16
17 def zhihuLogin():
18 """
19 獲取頁面的_xsrf
20 驗(yàn)證碼-抓包工具
21 發(fā)送post表單請(qǐng)求-抓包工具
22 :return:
23 """
24
25 # 相當(dāng)于urllib2,通過HTTPCookieProcessor()處理器類構(gòu)建一個(gè)處理器對(duì)象,
26 # 再用urllib2.build_opener構(gòu)建一個(gè)自定義的opener來保存cookie
27 # 構(gòu)建一個(gè)Session對(duì)象,可以保存頁面Cookie
28 sess = requests.session() # 創(chuàng)建一個(gè)能夠儲(chǔ)存cookie的opener
29
30 # 首先獲取登錄頁面,找到需要POST的數(shù)據(jù)(_xsrf),同時(shí)會(huì)記錄當(dāng)前網(wǎng)頁的Cookie值
31 # 也可以直接用requests.get("...")發(fā)送請(qǐng)求,但這樣就沒法保存cookie值了
32 # 獲取HTML內(nèi)容可以用.text/.content來獲取
33 html = sess.get('https://www.zhihu.com/#signin',headers=headers).text # get <==> 發(fā)送get請(qǐng)求
34
35 # 調(diào)用lxml解析庫
36 bs = BeautifulSoup(html, 'lxml')
37
38 # _xsrf 作用是防止CSRF攻擊(跨站請(qǐng)求偽造),通常叫跨域攻擊,是一種利用網(wǎng)站對(duì)用戶的一種信任機(jī)制來做壞事
39 # 跨域攻擊通常通過偽裝成網(wǎng)站信任的用戶的請(qǐng)求(利用Cookie),盜取用戶信息、欺騙web服務(wù)器
40 # 所以網(wǎng)站會(huì)通過設(shè)置一個(gè)隱藏字段來存放這個(gè)MD5字符串,這個(gè)字符串用來校驗(yàn)用戶Cookie和服務(wù)器Session的一種方式
41
42 # 找到name屬性值為 _xsrf 的input標(biāo)簽,再取出value 的值
43 _xsrf = bs.find("input", attrs={"name":"_xsrf"}).get("value") # 獲取_xsrf
44 # 根據(jù)UNIX時(shí)間戳,匹配出驗(yàn)證碼的URL地址
45 # 發(fā)送圖片的請(qǐng)求,獲取圖片數(shù)據(jù)流,
46 captcha_data = sess.get('https://www.zhihu.com/captcha.gif?r=%d&type=login'%(time.time()*1000),headers=headers).content
47 # 調(diào)用上面的方法(需要手動(dòng)輸入),獲取驗(yàn)證碼里的文字
48 captcha_text = captcha(captcha_data)
49 data = {
50 "_xsrf": _xsrf,
51 "phone_num": "xxx",
52 "password": "xxx",
53 "captcha": captcha_text
54 }
55 # 發(fā)送登錄需要的POST數(shù)據(jù),獲取登錄后的Cookie(保存在sess里)
56 sess.post('https://www.zhihu.com/login/phone_num',data=data,headers=headers)
57
58 # 用已有登錄狀態(tài)的Cookie發(fā)送請(qǐng)求,獲取目標(biāo)頁面源碼
59 response = sess.get("https://www.zhihu.com/people/peng-peng-34-48-53/activities",headers=headers)
60
61 with open("jiaxin.html",'wb') as f:
62 f.write(response.content)
63
64 if __name__ == '__main__':
65 zhihuLogin()
示例:通過bs獲取_xsrf登錄知乎
二、JSON與JsonPATH
JSON
json簡(jiǎn)單說就是javascript中的對(duì)象和數(shù)組,所以這兩種結(jié)構(gòu)就是對(duì)象和數(shù)組兩種結(jié)構(gòu),通過這兩種結(jié)構(gòu)可以表示各種復(fù)雜的結(jié)構(gòu)
對(duì)象:對(duì)象在js中表示為
{ }括起來的內(nèi)容,數(shù)據(jù)結(jié)構(gòu)為{ key:value, key:value, ... }的鍵值對(duì)的結(jié)構(gòu),在面向?qū)ο蟮恼Z言中,key為對(duì)象的屬性,value為對(duì)應(yīng)的屬性值,所以很容易理解,取值方法為 對(duì)象.key 獲取屬性值,這個(gè)屬性值的類型可以是數(shù)字、字符串、數(shù)組、對(duì)象這幾種。數(shù)組:數(shù)組在js中是中括號(hào)
[ ]括起來的內(nèi)容,數(shù)據(jù)結(jié)構(gòu)為["Python", "javascript", "C++", ...],取值方式和所有語言中一樣,使用索引獲取,字段值的類型可以是 數(shù)字、字符串、數(shù)組、對(duì)象幾種。
json模塊提供了四個(gè)功能:dumps、dump、loads、load,用于字符串 和 python數(shù)據(jù)類型間進(jìn)行轉(zhuǎn)換。
1. json.loads() 字符串 ==> python類型
把Json格式字符串解碼轉(zhuǎn)換成Python對(duì)象 從json到python的類型轉(zhuǎn)化對(duì)照如下:
import json
strList = '[1, 2, 3, 4]'
strDict = '{"city": "北京", "name": "大貓"}'
print(json.loads(strList))
# [1, 2, 3, 4]
print(json.loads(strDict)) # python3中json數(shù)據(jù)自動(dòng)按utf-8存儲(chǔ)
# {'city': '北京', 'name': '大貓'}
2. json.dumps() python類型 ==> 字符串
實(shí)現(xiàn)python類型轉(zhuǎn)化為json字符串,返回一個(gè)str對(duì)象 把一個(gè)Python對(duì)象編碼轉(zhuǎn)換成Json字符串
從python原始類型向json類型的轉(zhuǎn)化對(duì)照如下:
import json
dictStr = {"city": "北京", "name": "大貓"}
# 注意:json.dumps() 序列化時(shí)默認(rèn)使用的ascii編碼
# 添加參數(shù) ensure_ascii=False 禁用ascii編碼,按utf-8編碼
# chardet.detect()返回字典, 其中confidence是檢測(cè)精確度
print(json.dumps(dictStr))
# '{"city": "\u5317\u4eac", "name": "\u5927\u5218"}'
print(json.dumps(dictStr, ensure_ascii=False))
# {"city": "北京", "name": "大劉"}
3. json.dump() # 基本不用
將Python內(nèi)置類型序列化為json對(duì)象后寫入文件
import json
listStr = [{"city": "北京"}, {"name": "大劉"}]
json.dump(listStr, open("listStr.json","wb"), ensure_ascii=False)
dictStr = {"city": "北京", "name": "大劉"}
json.dump(dictStr, open("dictStr.json","w"), ensure_ascii=False)
4. json.load() # 基本不用
讀取文件中json形式的字符串元素 轉(zhuǎn)化成python類型
# json_load.py
import json
strList = json.load(open("listStr.json"))
print strList
# [{u'city': u'u5317u4eac'}, {u'name': u'u5927u5218'}]
strDict = json.load(open("dictStr.json"))
print strDict
# {u'city': u'u5317u4eac', u'name': u'u5927u5218'}
JsonPath
python3中沒有jsonpath,改為jsonpath_rw,用法不明
JsonPath 是一種信息抽取類庫,是從JSON文檔中抽取指定信息的工具,提供多種語言實(shí)現(xiàn)版本,包括:Javascript, Python, PHP 和 Java。
JsonPath 對(duì)于 JSON 來說,相當(dāng)于 XPATH 對(duì)于 XML。
下載地址:https://pypi.python.org/pypi/jsonpath
安裝方法:點(diǎn)擊
Download URL鏈接下載jsonpath,解壓之后執(zhí)行python setup.py install官方文檔:http://goessner.net/articles/JsonPath
JsonPath與XPath語法對(duì)比:
Json結(jié)構(gòu)清晰,可讀性高,復(fù)雜度低,非常容易匹配,下表中對(duì)應(yīng)了XPath的用法。
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3
4 import urllib2
5 # json解析庫,對(duì)應(yīng)到lxml
6 import json
7 # json的解析語法,對(duì)應(yīng)到xpath
8 import jsonpath
9
10 url = "http://www.lagou.com/lbs/getAllCitySearchLabels.json"
11 headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
12
13 request = urllib2.Request(url, headers = headers)
14
15 response = urllib2.urlopen(request)
16 # 取出json文件里的內(nèi)容,返回的格式是字符串
17 html = response.read()
18
19 # 把json形式的字符串轉(zhuǎn)換成python形式的Unicode字符串
20 unicodestr = json.loads(html)
21
22 # Python形式的列表
23 city_list = jsonpath.jsonpath(unicodestr, "$..name")
24
25 #for item in city_list:
26 # print item
27
28 # dumps()默認(rèn)中文為ascii編碼格式,ensure_ascii默認(rèn)為Ture
29 # 禁用ascii編碼格式,返回的Unicode字符串,方便使用
30 array = json.dumps(city_list, ensure_ascii=False)
31 #json.dumps(city_list)
32 #array = json.dumps(city_list)
33
34 with open("lagoucity.json", "w") as f:
35 f.write(array.encode("utf-8"))
示例:拉勾網(wǎng)json接口
三、多線程爬蟲案例
python多線程簡(jiǎn)介
一個(gè)CPU一次只能執(zhí)行一個(gè)進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài) 進(jìn)程里面包含的執(zhí)行單位叫線程,一個(gè)進(jìn)程包含多個(gè)線程 一個(gè)進(jìn)程里面的內(nèi)存空間是共享的,里面的線程都可以使用這個(gè)內(nèi)存空間 一個(gè)線程在使用這個(gè)共享空間時(shí),其他線程必須等他結(jié)束(通過加鎖實(shí)現(xiàn)) 鎖的作用:防止多個(gè)線程同時(shí)用這塊共享的內(nèi)存空間,先使用的線程會(huì)上一把鎖,其他線程想要用的話就要等他用完才可以進(jìn)去 python中的鎖(GIL) python的多線程很雞肋,所以scrapy框架用的是協(xié)程 python多進(jìn)程適用于:大量密集的并行計(jì)算 python多線程適用于:大量密集的I/O操作
Queue(隊(duì)列對(duì)象)
Queue是python中的標(biāo)準(zhǔn)庫,可以直接import Queue引用;隊(duì)列是線程間最常用的交換數(shù)據(jù)的形式
python下多線程的思考
對(duì)于資源,加鎖是個(gè)重要的環(huán)節(jié)。因?yàn)閜ython原生的list,dict等,都是not thread safe的。而Queue,是線程安全的,因此在滿足使用條件下,建議使用隊(duì)列
初始化: class Queue.Queue(maxsize) FIFO 先進(jìn)先出
包中的常用方法:
Queue.qsize() 返回隊(duì)列的大小
Queue.empty() 如果隊(duì)列為空,返回True,反之False
Queue.full() 如果隊(duì)列滿了,返回True,反之False
Queue.full 與 maxsize 大小對(duì)應(yīng)
Queue.get([block[, timeout]])獲取隊(duì)列,timeout等待時(shí)間
創(chuàng)建一個(gè)“隊(duì)列”對(duì)象
import Queue
myqueue = Queue.Queue(maxsize = 10)
將一個(gè)值放入隊(duì)列中
myqueue.put(10)
將一個(gè)值從隊(duì)列中取出
myqueue.get()
多線程示意圖
示例:多線程爬取糗事百科上的段子(好好看這個(gè))
import threading,json,time,requests
from lxml import etree
from queue import Queue
class ThreadCrawl(threading.Thread):
def __init__(self,thread_name,pageQueue,dataQueue):
super(ThreadCrawl,self).__init__() # 調(diào)用父類初始化方法
self.thread_name = thread_name # 線程名
self.pageQueue = pageQueue # 頁碼隊(duì)列
self.dataQueue = dataQueue # 數(shù)據(jù)隊(duì)列
self.headers = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"}
def run(self):
print("啟動(dòng)"+self.thread_name)
while not self.pageQueue.empty(): # 如果pageQueue為空,采集線程退出循環(huán) Queue.empty() 判斷隊(duì)列是否為空
try:
# 取出一個(gè)數(shù)字,先進(jìn)先出
# 可選參數(shù)block,默認(rèn)值為True
#1. 如果對(duì)列為空,block為True的話,不會(huì)結(jié)束,會(huì)進(jìn)入阻塞狀態(tài),直到隊(duì)列有新的數(shù)據(jù)
#2. 如果隊(duì)列為空,block為False的話,就彈出一個(gè)Queue.empty()異常,
page = self.pageQueue.get(False)
url = "http://www.qiushibaike.com/8hr/page/" + str(page) +"/"
html = requests.get(url,headers = self.headers).text
time.sleep(1) # 等待1s等他全部下完
self.dataQueue.put(html)
except Exception as e:
pass
print("結(jié)束" + self.thread_name)
class ThreadParse(threading.Thread):
def __init__(self,threadName,dataQueue,lock):
super(ThreadParse,self).__init__()
self.threadName = threadName
self.dataQueue = dataQueue
self.lock = lock # 文件讀寫鎖
self.headers = {"User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"}
def run(self):
print("啟動(dòng)"+self.threadName)
while not self.dataQueue.empty(): # 如果pageQueue為空,采集線程退出循環(huán)
try:
html = self.dataQueue.get() # 解析為HTML DOM
text = etree.HTML(html)
node_list = text.xpath('//div[contains(@id, "qiushi_tag_")]') # xpath返回的列表,這個(gè)列表就這一個(gè)參數(shù),用索引方式取出來,用戶名
for i in node_list:
username = i.xpath('.//h2')[0].text # 用戶名
user_img = i.xpath('.//img/@src')[0] # 用戶頭像連接
word_content = i.xpath('.//div[@class="content"]/span')[0].text # 文字內(nèi)容
img_content = i.xpath('.//img/@src') # 圖片內(nèi)容
zan = i.xpath('./div[@class="stats"]/span[@class="stats-vote"]/i')[0].text # 點(diǎn)贊
comments = i.xpath('./div[@class="stats"]/span[@class="stats-comments"]//i')[0].text # 評(píng)論
items = {
"username": username,
"image": user_img,
"word_content": word_content,
"img_content": img_content,
"zan": zan,
"comments": comments
}
# with 后面有兩個(gè)必須執(zhí)行的操作:__enter__ 和 _exit__
# 不管里面的操作結(jié)果如何,都會(huì)執(zhí)行打開、關(guān)閉
# 打開鎖、處理內(nèi)容、釋放鎖
with self.lock:
with open('qiushi-threading.json123','ab') as f:
# json.dumps()時(shí),里面一定要加 ensure_ascii = False 否則會(huì)以ascii嘛的形式進(jìn)行轉(zhuǎn)碼,文件中就不是中文了
f.write((self.threadName+json.dumps(items, ensure_ascii = False)).encode("utf-8") + b'
')
except Exception as e:
print(e)
def main():
pageQueue = Queue(10) # 頁碼的隊(duì)列,表示10個(gè)頁面,不寫表示不限制個(gè)數(shù)
for i in range(1,11): # 放入1~10的數(shù)字,先進(jìn)先出
pageQueue.put(i)
dataQueue = Queue() # 采集結(jié)果(每頁的HTML源碼)的數(shù)據(jù)隊(duì)列,參數(shù)為空表示不限制個(gè)數(shù)
crawlList = ["采集線程1號(hào)", "采集線程2號(hào)", "采集線程3號(hào)"] # 存儲(chǔ)三個(gè)采集線程的列表集合,留著后面join(等待所有子進(jìn)程完成在退出程序)
threadcrawl = []
for thread_name in crawlList:
thread = ThreadCrawl(thread_name,pageQueue,dataQueue)
thread.start()
threadcrawl.append(thread)
for i in threadcrawl:
i.join()
print('1')
lock = threading.Lock() # 創(chuàng)建鎖
# *** 解析線程一定要在采集線程join(結(jié)束)以后寫,否則會(huì)出現(xiàn)dataQueue.empty()=True(數(shù)據(jù)隊(duì)列為空),因?yàn)椴杉€程還沒往里面存東西呢 ***
parseList = ["解析線程1號(hào)","解析線程2號(hào)","解析線程3號(hào)"] # 三個(gè)解析線程的名字
threadparse = [] # 存儲(chǔ)三個(gè)解析線程,留著后面join(等待所有子進(jìn)程完成在退出程序)
for threadName in parseList:
thread = ThreadParse(threadName,dataQueue,lock)
thread.start()
threadparse.append(thread)
for j in threadparse:
j.join()
print('2')
print("謝謝使用!")
if __name__ == "__main__":
main()
四、動(dòng)態(tài)HTML處理
獲取JavaScript,jQuery,Ajax...加載的網(wǎng)頁數(shù)據(jù)
Selenium
Selenium是一個(gè)Web的自動(dòng)化測(cè)試工具,最初是為網(wǎng)站自動(dòng)化測(cè)試而開發(fā)的,類型像我們玩游戲用的按鍵精靈,可以按指定的命令自動(dòng)操作,不同是Selenium 可以直接運(yùn)行在瀏覽器上,它支持所有主流的瀏覽器(包括PhantomJS這些無界面的瀏覽器)。
Selenium 可以根據(jù)我們的指令,讓瀏覽器自動(dòng)加載頁面,獲取需要的數(shù)據(jù),甚至頁面截屏,或者判斷網(wǎng)站上某些動(dòng)作是否發(fā)生。
Selenium 自己不帶瀏覽器,不支持瀏覽器的功能,它需要與第三方瀏覽器結(jié)合在一起才能使用。但是我們有時(shí)候需要讓它內(nèi)嵌在代碼中運(yùn)行,所以我們可以用一個(gè)叫 PhantomJS 的工具代替真實(shí)的瀏覽器。
可以從 PyPI 網(wǎng)站下載 Selenium庫https://pypi.python.org/simple/selenium,也可以用 第三方管理器 pip用命令安裝:
pip install seleniumSelenium 官方參考文檔:http://selenium-python.readthedocs.io/index.html
PhantomJS
PhantomJS是一個(gè)基于Webkit的“無界面”(headless)瀏覽器,它會(huì)把網(wǎng)站加載到內(nèi)存并執(zhí)行頁面上的 JavaScript,因?yàn)椴粫?huì)展示圖形界面,所以運(yùn)行起來比完整的瀏覽器要高效。
如果我們把 Selenium 和 PhantomJS 結(jié)合在一起,就可以運(yùn)行一個(gè)非常強(qiáng)大的網(wǎng)絡(luò)爬蟲了,這個(gè)爬蟲可以處理 JavaScrip、Cookie、headers,以及任何我們真實(shí)用戶需要做的事情。
注意:PhantomJS 只能從它的官方網(wǎng)站http://phantomjs.org/download.html)下載。 因?yàn)?PhantomJS 是一個(gè)功能完善(雖然無界面)的瀏覽器而非一個(gè) Python 庫,所以它不需要像 Python 的其他庫一樣安裝,但我們可以通過Selenium調(diào)用PhantomJS來直接使用。
PhantomJS 官方參考文檔:http://phantomjs.org/documentation
快速入門
Selenium 庫里有個(gè)叫 WebDriver 的 API。WebDriver 有點(diǎn)兒像可以加載網(wǎng)站的瀏覽器,但是它也可以像 BeautifulSoup 或者其他 Selector 對(duì)象一樣用來查找頁面元素,與頁面上的元素進(jìn)行交互 (發(fā)送文本、點(diǎn)擊等),以及執(zhí)行其他動(dòng)作來運(yùn)行網(wǎng)絡(luò)爬蟲。
# 導(dǎo)入 webdriver
from selenium import webdriver
# 要想調(diào)用鍵盤按鍵操作需要引入keys包
from selenium.webdriver.common.keys import Keys
# 調(diào)用環(huán)境變量指定的PhantomJS瀏覽器創(chuàng)建瀏覽器對(duì)象
driver = webdriver.PhantomJS()
# 如果沒有在環(huán)境變量指定PhantomJS位置
# driver = webdriver.PhantomJS(executable_path="./phantomjs"))
# get方法會(huì)一直等到頁面被完全加載,然后才會(huì)繼續(xù)程序,通常測(cè)試會(huì)在這里選擇 time.sleep(2)
driver.get("http://www.baidu.com/")
# 獲取頁面名為 wrapper的id標(biāo)簽的文本內(nèi)容
data = driver.find_element_by_id("wrapper").text
# 打印數(shù)據(jù)內(nèi)容
print data
# 打印頁面標(biāo)題 "百度一下,你就知道"
print driver.title
# 生成當(dāng)前頁面快照并保存
driver.save_screenshot("baidu.png")
# id="kw"是百度搜索輸入框,輸入字符串"長(zhǎng)城"
driver.find_element_by_id("kw").send_keys(u"長(zhǎng)城")
# id="su"是百度搜索按鈕,click() 是模擬點(diǎn)擊
driver.find_element_by_id("su").click()
# 獲取新的頁面快照
driver.save_screenshot("長(zhǎng)城.png")
# 打印網(wǎng)頁渲染后的源代碼
print driver.page_source
# 獲取當(dāng)前頁面Cookie
print driver.get_cookies()
# ctrl+a 全選輸入框內(nèi)容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')
# ctrl+x 剪切輸入框內(nèi)容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')
# 輸入框重新輸入內(nèi)容
driver.find_element_by_id("kw").send_keys("itcast")
# 模擬Enter回車鍵
driver.find_element_by_id("su").send_keys(Keys.RETURN)
# 清除輸入框內(nèi)容
driver.find_element_by_id("kw").clear()
# 生成新的頁面快照
driver.save_screenshot("itcast.png")
# 獲取當(dāng)前url
print driver.current_url
# 關(guān)閉當(dāng)前頁面,如果只有一個(gè)頁面,會(huì)關(guān)閉瀏覽器
# driver.close()
# 關(guān)閉瀏覽器
driver.quit()
頁面操作
Selenium 的 WebDriver提供了各種方法來尋找元素,假設(shè)下面有一個(gè)表單輸入框:
<input type="text" name="user-name" id="passwd-id" />
那么:
# 獲取id標(biāo)簽值
element = driver.find_element_by_id("passwd-id")
# 獲取name標(biāo)簽值
element = driver.find_element_by_name("user-name")
# 獲取標(biāo)簽名值
element = driver.find_elements_by_tag_name("input")
# 也可以通過XPath來匹配
element = driver.find_element_by_xpath("http://input[@id='passwd-id']")
定位UI元素 (WebElements)
關(guān)于元素的選取,有如下的API 單個(gè)元素選取
find_element_by_id
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 ID
<div id="coolestWidgetEvah">...</div>
實(shí)現(xiàn)
element = driver.find_element_by_id("coolestWidgetEvah")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
element = driver.find_element(by=By.ID, value="coolestWidgetEvah")
By Class Name
<div class="cheese"><span>Cheddar</span></div><div class="cheese"><span>Gouda</span></div>
實(shí)現(xiàn)
cheeses = driver.find_elements_by_class_name("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheeses = driver.find_elements(By.CLASS_NAME, "cheese")
By Tag Name
<iframe src="..."></iframe>
實(shí)現(xiàn)
frame = driver.find_element_by_tag_name("iframe")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
frame = driver.find_element(By.TAG_NAME, "iframe")
By Name
<input name="cheese" type="text"/>
實(shí)現(xiàn)
cheese = driver.find_element_by_name("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.NAME, "cheese")
By Link Text
<a >cheese</a>
實(shí)現(xiàn)
cheese = driver.find_element_by_link_text("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.LINK_TEXT, "cheese")
By Partial Link Text
<a >search for cheese</a>>
實(shí)現(xiàn)
cheese = driver.find_element_by_partial_link_text("cheese")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.PARTIAL_LINK_TEXT, "cheese")
By CSS
<div id="food"><span class="dairy">milk</span><span class="dairy aged">cheese</span></div>
實(shí)現(xiàn)
cheese = driver.find_element_by_css_selector("#food span.dairy.aged")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
cheese = driver.find_element(By.CSS_SELECTOR, "#food span.dairy.aged")
By XPath
<input type="text" name="example" />
<INPUT type="text" name="other" />
實(shí)現(xiàn)
inputs = driver.find_elements_by_xpath("http://input")
------------------------ or -------------------------
from selenium.webdriver.common.by import By
inputs = driver.find_elements(By.XPATH, "http://input")
鼠標(biāo)動(dòng)作鏈
有些時(shí)候,我們需要再頁面上模擬一些鼠標(biāo)操作,比如雙擊、右擊、拖拽甚至按住不動(dòng)等,我們可以通過導(dǎo)入 ActionChains 類來做到:
示例:
#導(dǎo)入 ActionChains 類
from selenium.webdriver import ActionChains
# 鼠標(biāo)移動(dòng)到 ac 位置
ac = driver.find_element_by_xpath('element')
ActionChains(driver).move_to_element(ac).perform()
# 在 ac 位置單擊
ac = driver.find_element_by_xpath("elementA")
ActionChains(driver).move_to_element(ac).click(ac).perform()
# 在 ac 位置雙擊
ac = driver.find_element_by_xpath("elementB")
ActionChains(driver).move_to_element(ac).double_click(ac).perform()
# 在 ac 位置右擊
ac = driver.find_element_by_xpath("elementC")
ActionChains(driver).move_to_element(ac).context_click(ac).perform()
# 在 ac 位置左鍵單擊hold住
ac = driver.find_element_by_xpath('elementF')
ActionChains(driver).move_to_element(ac).click_and_hold(ac).perform()
# 將 ac1 拖拽到 ac2 位置
ac1 = driver.find_element_by_xpath('elementD')
ac2 = driver.find_element_by_xpath('elementE')
ActionChains(driver).drag_and_drop(ac1, ac2).perform()
填充表單
我們已經(jīng)知道了怎樣向文本框中輸入文字,但是有時(shí)候我們會(huì)碰到<select> </select>標(biāo)簽的下拉框。直接點(diǎn)擊下拉框中的選項(xiàng)不一定可行。
<select id="status" class="form-control valid" onchange="" name="status">
<option value=""></option>
<option value="0">未審核</option>
<option value="1">初審?fù)ㄟ^</option>
<option value="2">復(fù)審?fù)ㄟ^</option>
<option value="3">審核不通過</option>
</select>
Selenium專門提供了Select類來處理下拉框。 其實(shí) WebDriver 中提供了一個(gè)叫 Select 的方法,可以幫助我們完成這些事情:
# 導(dǎo)入 Select 類
from selenium.webdriver.support.ui import Select
# 找到 name 的選項(xiàng)卡
select = Select(driver.find_element_by_name('status'))
#
select.select_by_index(1)
select.select_by_value("0")
select.select_by_visible_text(u"未審核")
以上是三種選擇下拉框的方式,它可以根據(jù)索引來選擇,可以根據(jù)值來選擇,可以根據(jù)文字來選擇。注意:
index 索引從 0 開始
value是option標(biāo)簽的一個(gè)屬性值,并不是顯示在下拉框中的值
visible_text是在option標(biāo)簽文本的值,是顯示在下拉框的值
全部取消選擇怎么辦呢?很簡(jiǎn)單:
select.deselect_all()
彈窗處理
當(dāng)你觸發(fā)了某個(gè)事件之后,頁面出現(xiàn)了彈窗提示,處理這個(gè)提示或者獲取提示信息方法如下:
alert = driver.switch_to_alert()
頁面切換
一個(gè)瀏覽器肯定會(huì)有很多窗口,所以我們肯定要有方法來實(shí)現(xiàn)窗口的切換。切換窗口的方法如下:
driver.switch_to.window("this is window name")
也可以使用 window_handles 方法來獲取每個(gè)窗口的操作對(duì)象。例如:
for handle in driver.window_handles:
driver.switch_to_window(handle)
頁面前進(jìn)和后退
操作頁面的前進(jìn)和后退功能:
driver.forward() #前進(jìn)
driver.back() # 后退
Cookies
獲取頁面每個(gè)Cookies值,用法如下
for cookie in driver.get_cookies():
print "%s -> %s" % (cookie['name'], cookie['value'])
刪除Cookies,用法如下
# By name
driver.delete_cookie("CookieName")
# all
driver.delete_all_cookies()
頁面等待
注意:這是非常重要的一部分!!
現(xiàn)在的網(wǎng)頁越來越多采用了 Ajax 技術(shù),這樣程序便不能確定何時(shí)某個(gè)元素完全加載出來了。如果實(shí)際頁面等待時(shí)間過長(zhǎng)導(dǎo)致某個(gè)dom元素還沒出來,但是你的代碼直接使用了這個(gè)WebElement,那么就會(huì)拋出NullPointer的異常。
為了避免這種元素定位困難而且會(huì)提高產(chǎn)生 ElementNotVisibleException 的概率。所以 Selenium 提供了兩種等待方式,一種是隱式等待,一種是顯式等待。
隱式等待是等待特定的時(shí)間,顯式等待是指定某一條件直到這個(gè)條件成立時(shí)繼續(xù)執(zhí)行。
顯式等待
顯式等待指定某個(gè)條件,然后設(shè)置最長(zhǎng)等待時(shí)間。如果在這個(gè)時(shí)間還沒有找到元素,那么便會(huì)拋出異常了。
from selenium import webdriver
from selenium.webdriver.common.by import By
# WebDriverWait 庫,負(fù)責(zé)循環(huán)等待
from selenium.webdriver.support.ui import WebDriverWait
# expected_conditions 類,負(fù)責(zé)條件出發(fā)
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("http://www.xxxxx.com/loading")
try:
# 頁面一直循環(huán),直到 id="myDynamicElement" 出現(xiàn)
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "myDynamicElement"))
)
finally:
driver.quit()
如果不寫參數(shù),程序默認(rèn)會(huì) 0.5s 調(diào)用一次來查看元素是否已經(jīng)生成,如果本來元素就是存在的,那么會(huì)立即返回。
下面是一些內(nèi)置的等待條件,你可以直接調(diào)用這些條件,而不用自己寫某些等待條件了。
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
隱式等待
隱式等待比較簡(jiǎn)單,就是簡(jiǎn)單地設(shè)置一個(gè)等待時(shí)間,單位為秒。
from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get("http://www.xxxxx.com/loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")
當(dāng)然如果不設(shè)置,默認(rèn)等待時(shí)間為0。
示例一:使用Selenium + PhantomJS模擬豆瓣網(wǎng)登錄
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
from selenium import webdriver
# 實(shí)例化一個(gè)瀏覽器對(duì)象
driver = webdriver.PhantomJS("F:/Various plug-ins/phantomjs-2.1.1-windows/bin/phantomjs.exe")
driver.get("http://www.douban.com")
# 輸入賬號(hào)密碼
driver.find_element_by_name("form_email").send_keys("xx@qq.com") # 找到name=..的位置輸入值
driver.find_element_by_name("form_password").send_keys("xxx")
# 模擬點(diǎn)擊登錄
driver.find_element_by_xpath("http://input[@class='bn-submit']").click() # 按照xpath的方式找到登錄按鈕,點(diǎn)擊
# 等待3秒
time.sleep(3)
# 生成登陸后快照
driver.save_screenshot("douban.png")
with open("douban.html", "wb") as file:
file.write(driver.page_source.encode("utf-8")) # driver.page_source獲取當(dāng)前頁面的html
driver.quit() # 關(guān)閉瀏覽器
示例2:模擬動(dòng)態(tài)頁面的點(diǎn)擊(斗魚)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import unittest,time
from selenium import webdriver
from bs4 import BeautifulSoup as bs
class douyu(unittest.TestCase):
def setUp(self):
self.driver = webdriver.PhantomJS("F:/Various plug-ins/phantomjs-2.1.1-windows/bin/phantomjs.exe")
self.num = 0
def testDouyu(self):
self.driver.get("https://www.douyu.com/directory/all")
while True:
soup = bs(self.driver.page_source,'lxml')
names = soup.find_all('h3',attrs={"class" : "ellipsis"})
numbers = soup.find_all("span", attrs={"class" :"dy-num fr"})
for name, number in zip(names, numbers):
print u"觀眾人數(shù): -" + number.get_text().strip() + u"- 房間名: " + name.get_text().strip()
self.num += 1
if self.driver.page_source.find("shark-pager-disable-next") != -1:
break
time.sleep(0.5) # 要sleep一會(huì),等頁面加載完,否則會(huì)報(bào)錯(cuò)
self.driver.find_element_by_class_name("shark-pager-next").click()
# 單元測(cè)試模式的測(cè)試結(jié)束執(zhí)行的方法
def tearDown(self):
# 退出PhantomJS()瀏覽器
print "當(dāng)前網(wǎng)站直播人數(shù)" + str(self.num)
# print "當(dāng)前網(wǎng)站觀眾人數(shù)" + str(self.count)
self.driver.quit()
if __name__ == "__main__":
# 啟動(dòng)測(cè)試模塊,必須這樣寫
unittest.main()
示例3:執(zhí)行JavaScript語句,模擬滾動(dòng)條滾動(dòng)到底部
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from selenium import webdriver
import time
driver = webdriver.PhantomJS()
driver.get("https://movie.douban.com/typerank?type_name=劇情&type=11&interval_id=100:90&action=")
# 向下滾動(dòng)10000像素
js = "document.body.scrollTop=10000"
#js="var q=document.documentElement.scrollTop=10000"
time.sleep(3)
#查看頁面快照
driver.save_screenshot("douban.png")
# 執(zhí)行JS語句
driver.execute_script(js)
time.sleep(10)
#查看頁面快照
driver.save_screenshot("newdouban.png")
driver.quit()
示例4:模擬登錄kmust教務(wù)管理系統(tǒng)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys # 導(dǎo)入這個(gè)包,才能操作鍵盤
driver = webdriver.PhantomJS("F:/Various plug-ins/phantomjs-2.1.1-windows/bin/phantomjs.exe") # 實(shí)例化瀏覽器對(duì)象
driver.get("http://kmustjwcxk1.kmust.edu.cn/jwweb/") # 發(fā)送get請(qǐng)求,訪問昆工校園網(wǎng)
driver.find_element_by_id('m14').click() # 點(diǎn)擊用戶登錄按鈕,我們的校園網(wǎng)是采用的iframe加載的
driver.switch_to.frame("frmHomeShow") # 所以我們要采用driver.switch_to.frame("iframe標(biāo)簽名")方法,進(jìn)到iframe里面
with open("kmust.html", "wb") as file: # 保存一下,iframe里面的HTML
file.write(driver.page_source.encode("utf-8"))
driver.find_element_by_id('txt_asmcdefsddsd').send_keys('xxx') # 找到學(xué)號(hào)標(biāo)簽,添加數(shù)據(jù)
driver.find_element_by_id('txt_pewerwedsdfsdff').send_keys('xxx') # 找到密碼標(biāo)簽,添加數(shù)據(jù)
driver.find_element_by_id('txt_sdertfgsadscxcadsads').click() # 我們學(xué)校的驗(yàn)證碼需要點(diǎn)一下驗(yàn)證碼的input框才可以顯示出來
driver.save_screenshot("kmust.png") # 保存當(dāng)前界面的截屏
captcha = raw_input('請(qǐng)輸入驗(yàn)證碼:') # 打開截屏文件,這里需要手動(dòng)輸入
driver.find_element_by_id('txt_sdertfgsadscxcadsads').send_keys(captcha) # 找到驗(yàn)證碼標(biāo)簽,添加數(shù)據(jù)
driver.find_element_by_id("txt_sdertfgsadscxcadsads").send_keys(Keys.RETURN) # 模擬鍵盤的Enter鍵
time.sleep(1) # 網(wǎng)速太慢,讓他加載一會(huì)
driver.save_screenshot("kmust_ok.png") # 保存一下登錄成功的截圖
driver.switch_to.frame("banner") # 我們的教務(wù)網(wǎng)站是由下面4個(gè)iframe組成的
# driver.switch_to.frame("menu")
# driver.switch_to.frame("frmMain")
# driver.switch_to.frame("frmFoot")
with open("kmust-ok.html", "ab") as file: # 所以我們要進(jìn)入每個(gè)iframe,執(zhí)行相應(yīng)的操作(我不多說了,搶課腳本...)
file.write(driver.page_source.encode("utf-8")) # 保存下當(dāng)前iframe頁面的HTML數(shù)據(jù)
一個(gè)CPU一次只能執(zhí)行一個(gè)進(jìn)程,其他進(jìn)程處于非運(yùn)行狀態(tài)
進(jìn)程里面包含的執(zhí)行單位叫線程,一個(gè)進(jìn)程包含多個(gè)線程
一個(gè)進(jìn)程里面的內(nèi)存空間是共享的,里面的線程都可以使用這個(gè)內(nèi)存空間
一個(gè)線程在使用這個(gè)共享空間時(shí),其他線程必須等他結(jié)束(通過加鎖實(shí)現(xiàn))
鎖的作用:防止多個(gè)線程同時(shí)用這塊共享的內(nèi)存空間,先使用的線程會(huì)上一把鎖,其他線程想要用的話就要等他用完才可以進(jìn)去
python中的鎖(GIL)
python的多線程很雞肋,所以scrapy框架用的是協(xié)程
python多進(jìn)程適用于:大量密集的并行計(jì)算python多線程適用于:大量密集的I/O操作
總結(jié)
以上是生活随笔為你收集整理的python——BeautifulSoup4解析器,JSON与JsonPATH,多线程爬虫,动态HTML处理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浙政钉部分埋点成功排查
- 下一篇: 百万级数据量比对工作的一些整理