http header 消息通常被分為4個部分:general? header即頭部, request header即請求報文, response header即響應(yīng)報文, entity header實體報文。HTTP部分看我的《圖解HTTP》復(fù)習(xí),另外這地址?也很全了倔韭。
本文章分為介紹http的Requests和response常用的字段,以及怎么解析文件類型(怎么網(wǎng)頁徒扶,怎么解析Jason识腿,怎么解析csv)。怎么回調(diào)和保存到csv患蹂,爬蟲陷阱和解決方案等我還沒看夭禽,有時間再更新吧霞掺。
Python中Requests的官方文檔見:這里?
Requests部分:
Responses 部分:
scrapy中的Requests和Response對象
Request請求方法:
scrapy.http.Request(url[, callback, method, headers, body, cookies, meta, encoding='utf-8', priority=0, dont_filter=False, errback])
其中calback是回調(diào)函數(shù),headers是頭部信息驻粟,
包含cookie的發(fā)起請求寫法:
request_with_cookies = Request(url="http://www.example.com", cookies={'name': 'currency', 'value': 'USD', 'domain': 'example.com', 'path': '/currency'}根悼,meta={'dont_merge_cookies': True})
包含回調(diào)函數(shù)的請求寫法:
def parse_page1(self, response):
?????? item = MyItem()
?????? item['main_url'] = response.url
?????? request = scrapy.Request("http://www.example.com/some_page.html",??? callback=self.parse_page2)
?????? request.meta['item'] = item
??????? return request
def parse_page2(self, response):
????? item = response.meta['item']
????? item['other_url'] = response.url
????? return item
表單的請求方法:
from_response(response[, formname=None, formnumber=0, formdata=None, formxpath=None, clickdata=None, dont_click=False, ...])
formnumber:當(dāng)響應(yīng)內(nèi)容包含多個表單的時候用到
表單模擬登陸:
import scrapy
class LoginSpider(scrapy.Spider):
name = 'example.com' start_urls = ['http://www.example.com/users/login.php']
def parse(self, response):
???? return scrapy.FormRequest.from_response( response, formdata={'username': 'john', 'password': 'secret'}, callback=self.after_login )
def after_login(self, response):
# check login succeed before going on
if "authentication failed" in response.body:
??? self.log("Login failed", level=scrapy.log.ERROR)
???? return
Response對象:
屬性有:url, status, headers, body, flags凶异,request,metas挤巡,
文本處理常用方法:
response.body.decode(response.encoding)
response.xpath("**")
response.css("**")
上面是scrapy的部分剩彬,其他通用的內(nèi)容還有:
熟悉請求類型+會話Session+請求頭+請求實體(請求內(nèi)容+參數(shù)等)+響應(yīng)內(nèi)容+響應(yīng)狀態(tài)碼+cookie+重定向與請求歷史+超時處理等。
請求類型:get,post,put,delete等矿卑。
GET與POST方法有以下區(qū)別:
(1)?? 在客戶端喉恋,Get方式在通過URL提交數(shù)據(jù),數(shù)據(jù)在URL中可以看到母廷;POST方式轻黑,數(shù)據(jù)放置在HTML HEADER內(nèi)提交。
對于表單的提交方式琴昆,在服務(wù)器端只能用Request.QueryString來獲取Get方式提交來的數(shù)據(jù)氓鄙,用Post方式提交的數(shù)據(jù)只能用Request.Form來獲取
(2)?? GET方式提交的數(shù)據(jù)最多只能有1024 Byte,而POST則沒有此限制业舍。
(3)?? 一般來說抖拦,盡量避免使用Get方式提交表單,因為有可能會導(dǎo)致安全問題舷暮。比如說在登陸表單中用Get方式态罪,用戶輸入的用戶名和密碼將在地址欄中暴露無遺。但是在分頁程序中下面,用Get方式就比用Post好复颈。
會話:持續(xù)跟蹤會話信息,讓發(fā)出的所有請求之間保持 cookie沥割。
模擬瀏覽器耗啦,讓網(wǎng)絡(luò)機器人看起來像人類用戶。
用法:
from requests import Request
Session s = Session()
req = Request('GET', url,
data=data ,? ? ? ? ? ? #data={'key1':'value1','key2':['value2','value3']}
headers=headers? ? #headers={'user-agent':'my-app/0.0.1'}驯遇,模擬瀏覽器
#? files=files ,files = {'file': open('report.xls', 'rb')}? ? ? ? ? ? ? ? ? ?
)
prepped = s.prepare_request(req)
# do something with prepped.body
# do something with prepped.headers
resp = s.send(prepped,
stream=stream,
verify=verify,#驗證 SSL 證書
proxies=proxies,#使用代理proxies={"http":"https":"http://10.10.1.10:1080",}
cert=cert,
timeout=timeout #超時時間
)
print(resp.status_code)
r.headers? #訪問服務(wù)器返回給我們的響應(yīng)頭部信息
r.request.headers? #得到發(fā)送到服務(wù)器的請求的頭部
但是網(wǎng)站用cookie跟蹤你的訪問過程芹彬,如果發(fā)現(xiàn)了爬蟲異常行為就會中斷你的訪問蓄髓,比如特別快速地填寫表單叉庐,或者瀏覽大量頁面。要想正確使用cookie会喝,用PhantomJS方法陡叠。
from selenium import webdriverdriver = webdriver.PhantomJS(executable_path='')
driver.get("http://pythonscraping.com")
driver.implicitly_wait(1)
print(driver.get_cookies())
請求報文的媒體類型有:網(wǎng)頁,jason數(shù)據(jù)肢执,txt文本枉阵,csv文件,表單等预茄。pdf,word兴溜,docx這里不做解釋了侦厚。大家看《python網(wǎng)絡(luò)數(shù)據(jù)采集》這本書,針對每個媒體類型的分析和代碼都有寫拙徽。
解析網(wǎng)頁:
find方法刨沦,css選擇器,lxml膘怕,正則表達式想诅。這篇?文章解釋的很清楚了,要好好看暗盒摹@雌啤!
案例1:爬取一個詞條下的所有鏈接和子鏈接忘古。
思路:1)一個函數(shù)getLinks徘禁,可以用維基百科詞條/wiki/< 詞條名稱> 形式的URL 鏈接作為參數(shù),然后以同樣的形式返回一個列表髓堪,里面包含所有的詞條URL 鏈接晌坤。2) 一個主函數(shù),以某個起始詞條為參數(shù)調(diào)用getLinks旦袋,再從返回的URL 列表里隨機選擇一個詞條鏈接骤菠,再調(diào)用getLinks,直到我們主動停止疤孕,或者在新的頁面上沒有詞條鏈接了商乎,程序才停止運行。
完整的代碼如下所示:
from urllib.request import urlopen
from bs4 import BeautifulSoup
import datetime
import random
import re
random.seed(datetime.datetime.now())
def getLinks(articleUrl):
?????? html = urlopen("http://en.wikipedia.org"+articleUrl)
?????? bsObj = BeautifulSoup(html)
????? return bsObj.find("div", {"id":"bodyContent"}).findAll("a",
href=re.compile("^(/wiki/)((?!:).)*$"))
links = getLinks("/wiki/Kevin_Bacon")
while len(links) > 0:
????????? newArticle = links[random.randint(0, len(links)-1)].attrs["href"]
?????????? print(newArticle)
links = getLinks(newArticle)
案例2:訪問鏈接深度少于1000的整個網(wǎng)站
一個常用的費時的網(wǎng)站采集方法就是從頂級頁面開始(比如主頁)祭阀,然后搜索頁面上的所有鏈接鹉戚,形成列表。再去采集這些鏈接的每一個頁面专控,然后把在每個頁面上找到的鏈接形成新的列表抹凳,重復(fù)執(zhí)行下一輪采集。伦腐。在代碼運行時赢底,把已發(fā)現(xiàn)的所有鏈接都放到一起,并保存在方便查詢的列表里(下文示例指Python 的集合set 類型)柏蘑。為了全面地展示這個網(wǎng)絡(luò)數(shù)據(jù)采集示例是如何工作的幸冻,不再限制爬蟲采集的頁面范圍,只有“新”鏈接才會被采集咳焚,之后再從頁面中搜索其他鏈接洽损。只要遇到頁面就查找所有以/wiki/ 開頭的鏈接,也不考慮鏈接是不是包含分號革半。(提示:詞條鏈接不包含分號碑定,而文檔上傳頁面流码、討論頁面之類的頁面URL 鏈接都包含分號。)一開始延刘,用getLinks 處理一個空URL旅掂,其實是維基百科的主頁,因為在函數(shù)里空URL 就是http://en.wikipedia.org访娶。然后商虐,遍歷首頁上每個鏈接,并檢查是否已經(jīng)在全局變量集合pages 里面了(已經(jīng)采集的頁面集合)崖疤。如果不在秘车,就打印到屏幕上,并把鏈接加入pages 集合劫哼,再用getLinks 遞歸地處理這個鏈接叮趴。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
pages = set()
def getLinks(pageUrl):
global pages
html = urlopen("http://en.wikipedia.org"+pageUrl)
bsObj = BeautifulSoup(html)
for link in bsObj.findAll("a", href=re.compile("^(/wiki/)")):
??????? if 'href' in link.attrs:
????????????? if link.attrs['href'] not in pages:
???????????????? # 我們遇到了新頁面
???????????????? newPage = link.attrs['href']
???????????????? print(newPage)
??????????????? pages.add(newPage)
??????????????? getLinks(newPage)
? ? ? ? ? ? ? ? getLinks("")
案例3:收集整個網(wǎng)站的鏈接標(biāo)題和正文的第一個段落
和往常一樣,決定如何做好這些事情的第一步就是先觀察網(wǎng)站上的一些頁面权烧,然后擬定一個采集模式眯亦。通過觀察幾個維基百科頁面,包括詞條和非詞條頁面般码,比如隱私策略之類的頁面妻率,就會得出下面的規(guī)則。
from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
pages = set()
def getLinks(pageUrl):
global pages
html = urlopen("http://en.wikipedia.org"+pageUrl)
bsObj = BeautifulSoup(html)
try:
???????? print(bsObj.h1.get_text())
?????? ? print(bsObj.find(id="mw-content-text").findAll("p")[0])
???????? print(bsObj.find(id="ca-edit").find("span").find("a").attrs['href'])
?except AttributeError:
????????? print("頁面缺少一些屬性板祝!不過不用擔(dān)心宫静!")
for link in bsObj.findAll("a", href=re.compile("^(/wiki/)")):
??????? if 'href' in link.attrs:
??????????????? if link.attrs['href'] not in pages:
????????????????????? # 我們遇到了新頁面
???????????????????? newPage = link.attrs['href']
???????????????????? print("----------------\n"+newPage)
???????????????????? pages.add(newPage)
???????????????????? getLinks(newPage)
???????????????????? getLinks("")
解析jason:
import json
from urllib.request import urlopen
def getCountry(ipAddress):
response = urlopen("http://freegeoip.net/json/"+ipAddress).read().decode('utf-8')
responseJson = json.loads(response)
return responseJson.get("country_code")
print(getCountry("50.78.253.58"))
讀取文本:
1)讀取整個文件:
from urllib.request import urlopen
textPage = urlopen("http://www.pythonscraping.com/pages/warandpeace/chapter1.txt")
?print(textPage.read().rstrip())
2)逐行讀取文件并存入列表:
?from urllib.request import urlopen? ? ? ?
filename='pi_txt"
with open(filename) as file_ob:
?????? lines=file_ob.readline()
for line in lines:
?????? print(textPage.read().rstrip())
讀取CSV:
data = urlopen("http://pythonscraping.com/files/MontyPythonAlbums.csv")
.read().decode('ascii', 'ignore')
dataFile = StringIO(data)
csvReader = csv.reader(dataFile)
for row in csvReader:
? ? ? print(row)? #逐行讀取文件并存入到列表
提交表單:
import requests
params = {'firstname': 'Ryan', 'lastname': 'Mitchell'}
r = requests.post("http://pythonscraping.com/files/processing.php", data=params)
print(r.text)
響應(yīng)內(nèi)容:
文本:requests.get('https://github.com/timeline.json') >>>r.text
二進制:不寫了,這里券时,官方有孤里。
響應(yīng)狀態(tài)碼:
r.status_code,2XX OK橘洞;4XX 客戶端錯誤 捌袜;5XX 服務(wù)器錯誤響應(yīng)
設(shè)置超時時間:timeout=0.001
超時處理:
def? set_timeout(session,*arg)
retryTimes=20;
while retryTimes>0:
???????????? try?
?????????????????????? return session.post(*arg)
??????????? except:
????????????????????? ............
重定向與請求歷史:Response.history
用一種智能的方法來檢測客戶端重定向是否完成, 首先從頁面開始加載時就“ 監(jiān)視”DOM 中的一個元素炸枣, 然后重復(fù)調(diào)用這個元素直到Selenium 拋出一個StaleElementReferenceException 異常虏等;也就是說,元素不在頁面的DOM 里了抛虏,說明這時網(wǎng)站已經(jīng)跳轉(zhuǎn):
from selenium import webdriverimport time
from selenium.webdriver.remote.webelement
import WebElementfrom selenium.common.exceptions
import StaleElementReferenceException
def? waitForLoad(driver):
????????? elem = driver.find_element_by_tag_name("html")
????????? count = 0
????????? while True:
????????????????? count += 1
?????????????????? if count > 20:
?????????????????????????? print("Timing out after 10 seconds and returning")
?????????????????????????? return
????????? ? ??????? time.sleep(.5)
??????????????????? try:
?????????????????????????? elem == driver.find_element_by_tag_name("html")
??????????????????????????? except StaleElementReferenceException:
????????????????????? return
? driver =webdriver.PhantomJS(executable_path='')
? driver.get("http://pythonscraping.com/pages/javascript/redirectDemo1.html")
? waitForLoad(driver)
? print(driver.page_source)
下載數(shù)據(jù)寫入文件:
1)專門寫個抓取回調(diào)基類博其,供各個爬蟲方法使用? 2)為鏈接爬蟲添加緩存支持? 3)磁盤緩存 或者? 數(shù)據(jù)庫緩存
這部分沒看 套才,等有時間看吧迂猴。
流程是:調(diào)用open方法并賦予寫入模式w(讀取模式是r,附加模式是a),如果沒有這個文件會自動創(chuàng)建文件然后調(diào)用writte方法寫入數(shù)據(jù)背伴。
filename="wr.txt"
with open(filename,"w") as file_ob:
????? file_ob.write("i love solve questions")
爬蟲陷阱和困難有:
1)用隱含字段阻止網(wǎng)絡(luò)數(shù)據(jù)采集的方式主要有兩種沸毁。第一種是表單頁面上的一個字段可以用服務(wù)器生成的隨機變量表示峰髓。如果提交時這個值不在表單處理頁面上,服務(wù)器就有理由認(rèn)為這個提交不是從原始表單頁面上提交的息尺,而是由一個網(wǎng)絡(luò)機器人直接提交到表單處理頁面的携兵。繞開這個問題的最佳方法就是,首先采集表單所在頁面上生成的隨機變量搂誉,然后再提交到表單處理頁面徐紧。
2)第二種方式是“蜜罐”(honey pot)。如果表單里包含一個具有普通名稱的隱含字段(設(shè)置蜜罐圈套)炭懊,比如“用戶名”(username)或“郵箱地址”(email address)并级,設(shè)計不太好的網(wǎng)絡(luò)機器人往往不管這個字段是不是對用戶可見,直接填寫這個字段并向服務(wù)器提交侮腹,這樣就會中服務(wù)器的蜜罐圈套嘲碧。服務(wù)器會把所有隱含字段的真實值(或者與表單提交頁面的默認(rèn)值不同的值)都忽略,而且填寫隱含字段的訪問用戶也可能被網(wǎng)站封殺父阻。
如CSS 屬性設(shè)置display:none 進行隱藏愈涩;電話號碼字段name="phone" 是一個隱含的輸入字段;郵箱地址字段name="email" 是將元素向右移動50 000 像素(應(yīng)該會超出電腦顯示器的邊界)并隱藏滾動條
3)圖片驗證碼與動態(tài)網(wǎng)頁抓取JavaScript
google chrome加矛,右鍵-》審查元素-》network-》清空履婉,點擊加載更多--》出現(xiàn)對應(yīng)的get鏈接,尋找type=text/html斟览,點擊查看get參數(shù)或者request url谐鼎。
4)去重與自然語言處理NLTK
5)監(jiān)控網(wǎng)站隨時更新情況,并發(fā)下載趣惠。估算網(wǎng)站大小方法狸棍,是在谷歌搜索site:xxx.com看搜索結(jié)果統(tǒng)計多少條。
?
避免爬蟲陷阱的方案:
1)查看robots文件? ? 2)兩次下載之間添加延時??? 3)圖像識別:Pillow+Tesseract+NumPy? 4)cookie與代理服務(wù)器味悄,避免IP 地址被封殺? ? 5)Selenium 單元測試 等?
查看robots文件:用python的robotparser模塊解析robots文件草戈,以避免下載禁止爬取的URLcan_fetch(user_agent,url),如果結(jié)果值為false侍瑟,則是目標(biāo)網(wǎng)站robots.txt禁止用戶代理為user_agent變量值的爬蟲爬取網(wǎng)站.
兩次下載之間添加延時:設(shè)置延時的間隔值唐片,下次下載時判斷上次訪問時間self.domains.get(urlparse(url).netloc)與當(dāng)前時間的時間差sleepsecs是否在間隔時間內(nèi), 如果是涨颜,則線程睡眠time.sleep(sleepsecs)
其他的部分還沒學(xué)到费韭,先把網(wǎng)絡(luò)抓取部分先把它熟透了吧。