一、爬蟲之requests
a、介紹:使用requests可以模擬瀏覽器的請求,比起之前用到的urllib,requests模塊的api更加便捷(本質就是封裝了urllib3)
b、注意:requests發送請求是將網頁內容下載來以后,並不會執行js代碼,這需要我們自己分析目標站點然后發起新的requests請求
c、安裝:pip3 install requests
d、各種請求方式,常用的是requests.get()和requets.post()
二、基於get請求
a、基本請求
import requests response=requests.get('http://dig.chouti.com/') print(response.text)
b、帶參數get請求-----》》params
c、帶參數get請求-----》》headers
#通常我們在發送請求時都需要帶上請求頭,請求頭是將自身偽裝成瀏覽器的關鍵,常見的有用的請求頭如下 Host Referer #大型網站通常都會根據該參數判斷請求的來源 User-Agent #客戶端 Cookie #Cookie信息雖然包含在請求頭里,但requests模塊有單獨的參數來處理他,headers={}內就不要放它了
d、帶參數get請求-----》》cookies
三、基於post請求
a、介紹
#GET請求 HTTP默認的請求方法就是GET * 沒有請求體 * 數據必須在1K之內! * GET請求數據會暴露在瀏覽器的地址欄中 GET請求常用的操作: 1. 在瀏覽器的地址欄中直接給出URL,那么就一定是GET請求 2. 點擊頁面上的超鏈接也一定是GET請求 3. 提交表單時,表單默認使用GET請求,但可以設置為POST #POST請求 (1). 數據不會出現在地址欄中 (2). 數據的大小沒有上限 (3). 有請求體 (4). 請求體中如果存在中文,會使用URL編碼! #!!!requests.post()用法與requests.get()完全一致,特殊的是requests.post()有一個data參數,用來存放請求體數據 復制代碼
b、發送POST的請求,模擬瀏覽器的登錄行為
四、響應Response
a、response屬性
import requests respone=requests.get('http://www.jianshu.com') # respone屬性 print(respone.text) print(respone.content) print(respone.status_code) print(respone.headers) print(respone.cookies) print(respone.cookies.get_dict()) print(respone.cookies.items()) print(respone.url) print(respone.history) print(respone.encoding) #關閉:response.close() from contextlib import closing with closing(requests.get('xxx',stream=True)) as response: for line in response.iter_content(): pass
b、編碼問題
#編碼問題 import requests response=requests.get('http://www.autohome.com/news') # response.encoding='gbk' #汽車之家網站返回的頁面內容為gb2312編碼的,而requests的默認編碼為ISO-8859-1,如果不設置成gbk則中文亂碼 print(response.text)
c、獲取二進制
#stream參數:一點一點的取,比如下載視頻時,如果視頻100G,用response.content然后一下子寫到文件中是不合理的 import requests response=requests.get('https://gss3.baidu.com/6LZ0ej3k1Qd3ote6lo7D0j9wehsv/tieba-smallvideo-transcode/1767502_56ec685f9c7ec542eeaf6eac93a65dc7_6fe25cd1347c_3.mp4', stream=True) with open('b.mp4','wb') as f: for line in response.iter_content(): f.write(line)
d、解析json
#解析json import requests response=requests.get('http://httpbin.org/get') import json res1=json.loads(response.text) #太麻煩 res2=response.json() #直接獲取json數據 print(res1 == res2) #True
a、介紹
selenium最初是一個自動化測試工具,而爬蟲中使用它主要是為了解決requests無法直接執行JavaScript代碼的問題 selenium本質是通過驅動瀏覽器,完全模擬瀏覽器的操作,比如跳轉、輸入、點擊、下拉等,來拿到網頁渲染之后的結果,可支持多種瀏覽器 from selenium import webdriver browser=webdriver.Chrome() browser=webdriver.Firefox() browser=webdriver.PhantomJS() browser=webdriver.Safari() browser=webdriver.Edge()
b、安裝
#安裝:selenium+chromedriver pip3 install selenium 下載chromdriver.exe放到python安裝路徑的scripts目錄中即可,注意最新版本是2.29,並非2.9 國內鏡像網站地址:http://npm.taobao.org/mirrors/chromedriver/2.29/ 最新的版本去官網找:https://sites.google.com/a/chromium.org/chromedriver/downloads
#注意: selenium3默認支持的webdriver是Firfox,而Firefox需要安裝geckodriver 下載鏈接:https://github.com/mozilla/geckodriver/releases
六、選擇器
a、基本使用
from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #鍵盤按鍵操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待頁面加載某些元素 import time driver=webdriver.Chrome() driver.get('https://www.baidu.com') wait=WebDriverWait(driver,10) try: #===============所有方法=================== # 1、find_element_by_id # 2、find_element_by_link_text # 3、find_element_by_partial_link_text # 4、find_element_by_tag_name # 5、find_element_by_class_name # 6、find_element_by_name # 7、find_element_by_css_selector # 8、find_element_by_xpath # 強調: # 1、上述均可以改寫成find_element(By.ID,'kw')的形式 # 2、find_elements_by_xxx的形式是查找到多個元素,結果為列表 #===============示范用法=================== # 1、find_element_by_id print(driver.find_element_by_id('kw')) # 2、find_element_by_link_text # login=driver.find_element_by_link_text('登錄') # login.click() # 3、find_element_by_partial_link_text login=driver.find_elements_by_partial_link_text('錄')[0] login.click() # 4、find_element_by_tag_name print(driver.find_element_by_tag_name('a')) # 5、find_element_by_class_name button=wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'tang-pass-footerBarULogin'))) button.click() # 6、find_element_by_name input_user=wait.until(EC.presence_of_element_located((By.NAME,'userName'))) input_pwd=wait.until(EC.presence_of_element_located((By.NAME,'password'))) commit=wait.until(EC.element_to_be_clickable((By.ID,'TANGRAM__PSP_10__submit'))) input_user.send_keys('18611453110') input_pwd.send_keys('lhf@094573') commit.click() # 7、find_element_by_css_selector driver.find_element_by_css_selector('#kw') # 8、find_element_by_xpath time.sleep(5) finally: driver.close()
b、xpath
c、獲取標簽屬性
from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR from selenium.webdriver.common.keys import Keys #鍵盤按鍵操作 from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait #等待頁面加載某些元素 browser=webdriver.Chrome() browser.get('https://www.amazon.cn/') wait=WebDriverWait(browser,10) wait.until(EC.presence_of_element_located((By.ID,'cc-lm-tcgShowImgContainer'))) tag=browser.find_element(By.CSS_SELECTOR,'#cc-lm-tcgShowImgContainer img') #獲取標簽屬性, print(tag.get_attribute('src')) #獲取標簽ID,位置,名稱,大小(了解) print(tag.id) print(tag.location) print(tag.tag_name) print(tag.size) browser.close()
七、等待元素加載
a、selenium只是模擬瀏覽器的行為,而瀏覽器解析頁面是需要時間的(執行css,js),一些元素可能需要過一段時間才能加載出來,為了保證能查找到元素,必須等待
b、等待的方式分兩種:
隱式等待:在browser.get('xxx')前就設置,針對所有元素有效
顯式等待:在browser.get('xxx')之后設置,只針對某個元素有效
八、爬蟲之解析庫----re,beautifulsoup、pyquery
a、介紹
Beautiful Soup 是一個可以從HTML或XML文件中提取數據的Python庫.它能夠通過你喜歡的轉換器實現慣用的文檔導航,查找,
修改文檔的方式.Beautiful Soup會幫你節省數小時甚至數天的工作時間.你可能在尋找 Beautiful Soup3 的文檔,Beautiful Soup 3
目前已經停止開發,官網推薦在現在的項目中使用Beautiful Soup 4, 移植到BS4
b、安裝
安裝:Beautifulsoup4
pip3 install beautifulsoup4
安裝解釋器:
Beautiful Soup支持Python標准庫中的HTML解析器,還支持一些第三方的解析器,其中一個是 lxml .根據操作系統不同,可以選擇下列方法來安裝lxml:
九、遍歷文檔數
#遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個 #1、用法 #2、獲取標簽的名稱 #3、獲取標簽的屬性 #4、獲取標簽的內容 #5、嵌套選擇 #6、子節點、子孫節點 #7、父節點、祖先節點 #8、兄弟節點
#遍歷文檔樹:即直接通過標簽名字選擇,特點是選擇速度快,但如果存在多個相同的標簽則只返回第一個 html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p id="my p" class="title"><b id="bbb" class="boldest">The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ #1、用法 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') # soup=BeautifulSoup(open('a.html'),'lxml') print(soup.p) #存在多個相同的標簽則只返回第一個 print(soup.a) #存在多個相同的標簽則只返回第一個 #2、獲取標簽的名稱 print(soup.p.name) #3、獲取標簽的屬性 print(soup.p.attrs) #4、獲取標簽的內容 print(soup.p.string) # p下的文本只有一個時,取到,否則為None print(soup.p.strings) #拿到一個生成器對象, 取到p下所有的文本內容 print(soup.p.text) #取到p下所有的文本內容 for line in soup.stripped_strings: #去掉空白 print(line) ''' 如果tag包含了多個子節點,tag就無法確定 .string 方法應該調用哪個子節點的內容, .string 的輸出結果是 None,如果只有一個子節點那么就輸出該子節點的文本,比如下面的這種結構,soup.p.string 返回為None,但soup.p.strings就可以找到所有文本 <p id='list-1'> 哈哈哈哈 <a class='sss'> <span> <h1>aaaa</h1> </span> </a> <b>bbbbb</b> </p> ''' #5、嵌套選擇 print(soup.head.title.string) print(soup.body.a.string) #6、子節點、子孫節點 print(soup.p.contents) #p下所有子節點 print(soup.p.children) #得到一個迭代器,包含p下所有子節點 for i,child in enumerate(soup.p.children): print(i,child) print(soup.p.descendants) #獲取子孫節點,p下所有的標簽都會選擇出來 for i,child in enumerate(soup.p.descendants): print(i,child) #7、父節點、祖先節點 print(soup.a.parent) #獲取a標簽的父節點 print(soup.a.parents) #找到a標簽所有的祖先節點,父親的父親,父親的父親的父親... #8、兄弟節點 print('=====>') print(soup.a.next_sibling) #下一個兄弟 print(soup.a.previous_sibling) #上一個兄弟 print(list(soup.a.next_siblings)) #下面的兄弟們=>生成器對象 print(soup.a.previous_siblings) #上面的兄弟們=>生成器對象 用法
十、搜索文檔數
a、五種過濾器
#1、五種過濾器: 字符串、正則表達式、列表、True、方法 #1.1、字符串:即標簽名 print(soup.find_all('b')) #1.2、正則表達式 import re print(soup.find_all(re.compile('^b'))) #找出b開頭的標簽,結果有body和b標簽 #1.3、列表:如果傳入列表參數,Beautiful Soup會將與列表中任一元素匹配的內容返回.下面代碼找到文檔中所有<a>標簽和<b>標簽: print(soup.find_all(['a','b'])) #1.4、True:可以匹配任何值,下面代碼查找到所有的tag,但是不會返回字符串節點 print(soup.find_all(True)) for tag in soup.find_all(True): print(tag.name) #1.5、方法:如果沒有合適過濾器,那么還可以定義一個方法,方法只接受一個元素參數 ,如果這個方法返回 True 表示當前元素匹配並且被找到,如果不是則反回 False def has_class_but_no_id(tag): return tag.has_attr('class') and not tag.has_attr('id')
b、find_all
#2、find_all( name , attrs , recursive , text , **kwargs ) #2.1、name: 搜索name參數的值可以使任一類型的 過濾器 ,字符竄,正則表達式,列表,方法或是 True . print(soup.find_all(name=re.compile('^t'))) #2.2、keyword: key=value的形式,value可以是過濾器:字符串 , 正則表達式 , 列表, True . print(soup.find_all(id=re.compile('my'))) print(soup.find_all(href=re.compile('lacie'),id=re.compile('\d'))) #注意類要用class_ print(soup.find_all(id=True)) #查找有id屬性的標簽 # 有些tag屬性在搜索不能使用,比如HTML5中的 data-* 屬性: data_soup = BeautifulSoup('<div data-foo="value">foo!</div>','lxml') # data_soup.find_all(data-foo="value") #報錯:SyntaxError: keyword can't be an expression # 但是可以通過 find_all() 方法的 attrs 參數定義一個字典參數來搜索包含特殊屬性的tag: print(data_soup.find_all(attrs={"data-foo": "value"})) # [<div data-foo="value">foo!</div>] #2.3、按照類名查找,注意關鍵字是class_,class_=value,value可以是五種選擇器之一 print(soup.find_all('a',class_='sister')) #查找類為sister的a標簽 print(soup.find_all('a',class_='sister ssss')) #查找類為sister和sss的a標簽,順序錯誤也匹配不成功 print(soup.find_all(class_=re.compile('^sis'))) #查找類為sister的所有標簽 #2.4、attrs print(soup.find_all('p',attrs={'class':'story'})) #2.5、text: 值可以是:字符,列表,True,正則 print(soup.find_all(text='Elsie')) print(soup.find_all('a',text='Elsie')) #2.6、limit參數:如果文檔樹很大那么搜索會很慢.如果我們不需要全部結果,可以使用 limit 參數限制返回結果的數量.效果與SQL中的limit關鍵字類似,當搜索到的結果數量達到 limit 的限制時,就停止搜索返回結果 print(soup.find_all('a',limit=2)) #2.7、recursive:調用tag的 find_all() 方法時,Beautiful Soup會檢索當前tag的所有子孫節點,如果只想搜索tag的直接子節點,可以使用參數 recursive=False . print(soup.html.find_all('a')) print(soup.html.find_all('a',recursive=False)) ''' 像調用 find_all() 一樣調用tag find_all() 幾乎是Beautiful Soup中最常用的搜索方法,所以我們定義了它的簡寫方法. BeautifulSoup 對象和 tag 對象可以被當作一個方法來使用,這個方法的執行結果與調用這個對象的 find_all() 方法相同,下面兩行代碼是等價的: soup.find_all("a") soup("a") 這兩行代碼也是等價的: soup.title.find_all(text=True) soup.title(text=True) '''
c、find
#3、find( name , attrs , recursive , text , **kwargs ) find_all() 方法將返回文檔中符合條件的所有tag,盡管有時候我們只想得到一個結果.比如文檔中只有一個<body>標簽,那么使用 find_all() 方法來查找<body>標簽就不太合適, 使用 find_all 方法並設置 limit=1 參數不如直接使用 find() 方法.下面兩行代碼是等價的: soup.find_all('title', limit=1) # [<title>The Dormouse's story</title>] soup.find('title') # <title>The Dormouse's story</title> 唯一的區別是 find_all() 方法的返回結果是值包含一個元素的列表,而 find() 方法直接返回結果. find_all() 方法沒有找到目標是返回空列表, find() 方法找不到目標時,返回 None . print(soup.find("nosuchtag")) # None soup.head.title 是 tag的名字 方法的簡寫.這個簡寫的原理就是多次調用當前tag的 find() 方法: soup.head.title # <title>The Dormouse's story</title> soup.find("head").find("title") # <title>The Dormouse's story</title>
d、css選擇器
#1、CSS選擇器 print(soup.p.select('.sister')) print(soup.select('.sister span')) print(soup.select('#link1')) print(soup.select('#link1 span')) print(soup.select('#list-2 .element.xxx')) print(soup.select('#list-2')[0].select('.element')) #可以一直select,但其實沒必要,一條select就可以了 # 2、獲取屬性 print(soup.select('#list-2 h1')[0].attrs) # 3、獲取內容 print(soup.select('#list-2 h1')[0].get_text())
十一、爬蟲之MongoDB
a、簡介
MongoDB是一款強大、靈活、且易於擴展的通用型數據庫
b、易用性
MongoDB是一個面向文檔(document-oriented)的數據庫,而不是關系型數據庫。
不采用關系型主要是為了獲得更好得擴展性。當然還有一些其他好處,與關系數據庫相比,面向文檔的數據庫不再有“行“(row)的概念取而代之的是更為靈活的“文檔”(document)模型。
通過在文檔中嵌入文檔和數組,面向文檔的方法能夠僅使用一條記錄來表現復雜的層級關系,這與現代的面向對象語言的開發者對數據的看法一致。
另外,不再有預定義模式(predefined schema):文檔的鍵(key)和值(value)不再是固定的類型和大小。由於沒有固定的模式,
根據需要添加或刪除字段變得更容易了。通常由於開發者能夠進行快速迭代,所以開發進程得以加快。而且,實驗更容易進行。開發者能嘗試大量的數據模型,從中選一個最好的。
c、易擴展性
應用程序數據集的大小正在以不可思議的速度增長。隨着可用帶寬的增長和存儲器價格的下降,即使是一個小規模的應用程序,需要存儲的數據量也可能大的驚人,甚至超出
了很多數據庫的處理能力。過去非常罕見的T級數據,現在已經是司空見慣了。
由於需要存儲的數據量不斷增長,開發者面臨一個問題:應該如何擴展數據庫,分為縱向擴展和橫向擴展,縱向擴展是最省力的做法,但缺點是大型機一般都非常貴,而且
當數據量達到機器的物理極限時,花再多的錢也買不到更強的機器了,此時選擇橫向擴展更為合適,但橫向擴展帶來的另外一個問題就是需要管理的機器太多。
MongoDB的設計采用橫向擴展。面向文檔的數據模型使它能很容易地在多台服務器之間進行數據分割。MongoDB能夠自動處理跨集群的數據和負載,自動重新分配文檔,以及將
用戶的請求路由到正確的機器上。這樣,開發者能夠集中精力編寫應用程序,而不需要考慮如何擴展的問題。如果一個集群需要更大的容量,只需要向集群添加新服務器,
MongoDB就會自動將現有的數據向新服務器傳送
d、豐富的功能
MongoDB作為一款通用型數據庫,除了能夠創建、讀取、更新和刪除數據之外,還提供了一系列不斷擴展的獨特功能 #1、索引 支持通用二級索引,允許多種快速查詢,且提供唯一索引、復合索引、地理空間索引、全文索引 #2、聚合 支持聚合管道,用戶能通過簡單的片段創建復雜的集合,並通過數據庫自動優化 #3、特殊的集合類型 支持存在時間有限的集合,適用於那些將在某個時刻過期的數據,如會話session。類似地,MongoDB也支持固定大小的集合,用於保存近期數據,如日志 #4、文件存儲 支持一種非常易用的協議,用於存儲大文件和文件元數據。MongoDB並不具備一些在關系型數據庫中很普遍的功能,如鏈接join和復雜的多行事務。省略 這些的功能是處於架構上的考慮,或者說為了得到更好的擴展性,因為在分布式系統中這兩個功能難以高效地實
e、卓越的性能
MongoDB的一個主要目標是提供卓越的性能,這很大程度上決定了MongoDB的設計。MongoDB把盡可能多的內存用作緩存cache,視圖為每次查詢自動選擇正確的索引。
總之各方面的設計都旨在保持它的高性能
雖然MongoDB非常強大並試圖保留關系型數據庫的很多特性,但它並不追求具備關系型數據庫的所有功能。只要有可能,數據庫服務器就會將處理邏輯交給客戶端。
這種精簡方式的設計是MongoDB能夠實現如此高性能的原因之一
十二、MongoDB基礎
a、文檔是MongoDB的核心概念。文檔就是鍵值對的一個有序集{'msg':'hello','foo':3}。類似於python中的有序字典
需要注意的是: #1、文檔中的鍵/值對是有序的。 #2、文檔中的值不僅可以是在雙引號里面的字符串,還可以是其他幾種數據類型(甚至可以是整個嵌入的文檔)。 #3、MongoDB區分類型和大小寫。 #4、MongoDB的文檔不能有重復的鍵。 #5、文檔中的值可以是多種不同的數據類型,也可以是一個完整的內嵌文檔。文檔的鍵是字符串。除了少數例外情況,鍵可以使用任意UTF-8字符。 文檔鍵命名規范: #1、鍵不能含有\0 (空字符)。這個字符用來表示鍵的結尾。 #2、.和$有特別的意義,只有在特定環境下才能使用。 #3、以下划線"_"開頭的鍵是保留的(不是嚴格要求的)。
b、集合就是一組文檔。如果將MongoDB中的一個文檔比喻為關系型數據的一行,那么一個集合就是相當於一張表
#1、集合存在於數據庫中,通常情況下為了方便管理,不同格式和類型的數據應該插入到不同的集合,但其實集合沒有固定的結構,這意味着我們完全可以把不同格式和類型的數據統統插入一個集合中。 #2、組織子集合的方式就是使用“.”,分隔不同命名空間的子集合。 比如一個具有博客功能的應用可能包含兩個集合,分別是blog.posts和blog.authors,這是為了使組織結構更清晰,這里的blog集合(這個集合甚至不需要存在)跟它的兩個子集合沒有任何關系。 在MongoDB中,使用子集合來組織數據非常高效,值得推薦 #3、當第一個文檔插入時,集合就會被創建。合法的集合名: 集合名不能是空字符串""。 集合名不能含有\0字符(空字符),這個字符表示集合名的結尾。 集合名不能以"system."開頭,這是為系統集合保留的前綴。 用戶創建的集合名字不能含有保留字符。有些驅動程序的確支持在集合名里面包含,這是因為某些系統生成的集合中包含該字符。除非你要訪問這種系統創建的集合,否則千萬不要在名字里出現$。
c、數據庫:在MongoDB中,多個文檔組成集合,多個集合可以組成數據庫
數據庫也通過名字來標識。數據庫名可以是滿足以下條件的任意UTF-8字符串: #1、不能是空字符串("")。 #2、不得含有' '(空格)、.、$、/、\和\0 (空字符)。 #3、應全部小寫。 #4、最多64字節。 有一些數據庫名是保留的,可以直接訪問這些有特殊作用的數據庫。 #1、admin: 從身份認證的角度講,這是“root”數據庫,如果將一個用戶添加到admin數據庫,這個用戶將自動獲得所有數據庫的權限。
再者,一些特定的服務器端命令也只能從admin數據庫運行,如列出所有數據庫或關閉服務器 #2、local: 這個數據庫永遠都不可以復制,且一台服務器上的所有本地集合都可以存儲在這個數據庫中 #3、config: MongoDB用於分片設置時,分片信息會存儲在config數據庫中
d、強調:把數據庫名添加到集合名前,得到集合的完全限定名,即命名空間
例如:
如果要使用cms數據庫中的blog.posts集合,這個集合的命名空間就是
cmd.blog.posts。命名空間的長度不得超過121個字節,且在實際使用中應該小於100個字節
十三、CRUD操作
a、數據庫操作
#1、增 use config #如果數據庫不存在,則創建數據庫,否則切換到指定數據庫。 #2、查 show dbs #查看所有 可以看到,我們剛創建的數據庫config並不在數據庫的列表中, 要顯示它,我們需要向config數據庫插入一些數據。 db.table1.insert({'a':1}) #3、刪 use config #先切換到要刪的庫下 db.dropDatabase() #刪除當前庫
b、集合操作
#1、增 當第一個文檔插入時,集合就會被創建 > use database1 switched to db database1 > db.table1.insert({'a':1}) WriteResult({ "nInserted" : 1 }) > db.table2.insert({'b':2}) WriteResult({ "nInserted" : 1 }) #2、查 > show tables table1 table2 #3、刪 > db.table1.drop() true > show tables table2
d、文檔操作
增:
查:
# SQL:=,!=,>,<,>=,<= # MongoDB:{key:value}代表什么等於什么,"$ne","$gt","$lt","gte","lte",其中"$ne"能用於所有數據類型 #1、select * from db1.user where name = "alex"; db.user.find({'name':'alex'}) #2、select * from db1.user where name != "alex"; db.user.find({'name':{"$ne":'alex'}}) #3、select * from db1.user where id > 2; db.user.find({'_id':{'$gt':2}}) #4、select * from db1.user where id < 3; db.user.find({'_id':{'$lt':3}}) #5、select * from db1.user where id >= 2; db.user.find({"_id":{"$gte":2,}}) #6、select * from db1.user where id <= 2; db.user.find({"_id":{"$lte":2}})
# SQL:and,or,not # MongoDB:字典中逗號分隔的多個條件是and關系,"$or"的條件放到[]內,"$not" #1、select * from db1.user where id >= 2 and id < 4; db.user.find({'_id':{"$gte":2,"$lt":4}}) #2、select * from db1.user where id >= 2 and age < 40; db.user.find({"_id":{"$gte":2},"age":{"$lt":40}}) #3、select * from db1.user where id >= 5 or name = "alex"; db.user.find({ "$or":[ {'_id':{"$gte":5}}, {"name":"alex"} ] }) #4、select * from db1.user where id % 2=1; db.user.find({'_id':{"$mod":[2,1]}}) #5、上題,取反 db.user.find({'_id':{"$not":{"$mod":[2,1]}}})
# SQL:in,not in # MongoDB:"$in","$nin" #1、select * from db1.user where age in (20,30,31); db.user.find({"age":{"$in":[20,30,31]}}) #2、select * from db1.user where name not in ('alex','yuanhao'); db.user.find({"name":{"$nin":['alex','yuanhao']}}
# SQL: regexp 正則 # MongoDB: /正則表達/i #1、select * from db1.user where name regexp '^j.*?(g|n)$'; db.user.find({'name':/^j.*?(g|n)$/i})
#1、查看有dancing愛好的人 db.user.find({'hobbies':'dancing'}) #2、查看既有dancing愛好又有tea愛好的人 db.user.find({ 'hobbies':{ "$all":['dancing','tea'] } }) #3、查看第4個愛好為tea的人 db.user.find({"hobbies.3":'tea'}) #4、查看所有人最后兩個愛好 db.user.find({},{'hobbies':{"$slice":-2},"age":0,"_id":0,"name":0,"addr":0}) #5、查看所有人的第2個到第3個愛好 db.user.find({},{'hobbies':{"$slice":[1,2]},"age":0,"_id":0,"name":0,"addr":0}) > db.blog.find().pretty() { "_id" : 1, "name" : "alex意外死亡的真相", "comments" : [ { "name" : "egon", "content" : "alex是誰???", "thumb" : 200 }, { "name" : "wxx", "content" : "我去,真的假的", "thumb" : 300 }, { "name" : "yxx", "content" : "吃喝嫖賭抽,欠下兩個億", "thumb" : 40 }, { "name" : "egon", "content" : "xxx", "thumb" : 0 } ] } db.blog.find({},{'comments':{"$slice":-2}}).pretty() #查詢最后兩個 db.blog.find({},{'comments':{"$slice":[1,2]}}).pretty() #查詢1到2
# 排序:--1代表升序,-1代表降序 db.user.find().sort({"name":1,}) db.user.find().sort({"age":-1,'_id':1})
# 分頁:--limit代表取多少個document,skip代表跳過前多少個document。 db.user.find().sort({'age':1}).limit(1).skip(2)
# 獲取數量 db.user.count({'age':{"$gt":30}}) --或者 db.user.find({'age':{"$gt":30}}).count()
#1、{'key':null} 匹配key的值為null或者沒有這個key db.t2.insert({'a':10,'b':111}) db.t2.insert({'a':20}) db.t2.insert({'b':null}) > db.t2.find({"b":null}) { "_id" : ObjectId("5a5cc2a7c1b4645aad959e5a"), "a" : 20 } { "_id" : ObjectId("5a5cc2a8c1b4645aad959e5b"), "b" : null } #2、查找所有 db.user.find() #等同於db.user.find({}) db.user.find().pretty() #3、查找一個,與find用法一致,只是只取匹配成功的第一個 db.user.findOne({"_id":{"$gt":3}})
改:
update() 方法用於更新已存在的文檔。語法格式如下: db.collection.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> } ) 參數說明:對比update db1.t1 set name='EGON',sex='Male' where name='egon' and age=18; query : 相當於where條件。 update : update的對象和一些更新的操作符(如$,$inc...等,相當於set后面的 upsert : 可選,默認為false,代表如果不存在update的記錄不更新也不插入,設置為true代表插入。 multi : 可選,默認為false,代表只更新找到的第一條記錄,設為true,代表更新找到的全部記錄。 writeConcern :可選,拋出異常的級別。 更新操作是不可分割的:若兩個更新同時發送,先到達服務器的先執行,然后執行另外一個,不會破壞文檔。
#注意:除非是刪除,否則_id是始終不會變的 #1、覆蓋式: db.user.update({'age':20},{"name":"Wxx","hobbies_count":3}) 是用{"_id":2,"name":"Wxx","hobbies_count":3}覆蓋原來的記錄 #2、一種最簡單的更新就是用一個新的文檔完全替換匹配的文檔。這適用於大規模式遷移的情況。例如 var obj=db.user.findOne({"_id":2}) obj.username=obj.name+'SB' obj.hobbies_count++ delete obj.age db.user.update({"_id":2},obj)
#設置:$set 通常文檔只會有一部分需要更新。可以使用原子性的更新修改器,指定對文檔中的某些字段進行更新。 更新修改器是種特殊的鍵,用來指定復雜的更新操作,比如修改、增加后者刪除 #1、update db1.user set name="WXX" where id = 2 db.user.update({'_id':2},{"$set":{"name":"WXX",}}) #2、沒有匹配成功則新增一條{"upsert":true} db.user.update({'_id':6},{"$set":{"name":"egon","age":18}},{"upsert":true}) #3、默認只改匹配成功的第一條,{"multi":改多條} db.user.update({'_id':{"$gt":4}},{"$set":{"age":28}}) db.user.update({'_id':{"$gt":4}},{"$set":{"age":38}},{"multi":true}) #4、修改內嵌文檔,把名字為alex的人所在的地址國家改成Japan db.user.update({'name':"alex"},{"$set":{"addr.country":"Japan"}}) #5、把名字為alex的人的地2個愛好改成piao db.user.update({'name':"alex"},{"$set":{"hobbies.1":"piao"}}) #6、刪除alex的愛好,$unset db.user.update({'name':"alex"},{"$unset":{"hobbies":""}})
往數組內添加元素:$push #1、為名字為yuanhao的人添加一個愛好read db.user.update({"name":"yuanhao"},{"$push":{"hobbies":"read"}}) #2、為名字為yuanhao的人一次添加多個愛好tea,dancing db.user.update({"name":"yuanhao"},{"$push":{ "hobbies":{"$each":["tea","dancing"]} }}) 按照位置且只能從開頭或結尾刪除元素:$pop #3、{"$pop":{"key":1}} 從數組末尾刪除一個元素 db.user.update({"name":"yuanhao"},{"$pop":{ "hobbies":1} }) #4、{"$pop":{"key":-1}} 從頭部刪除 db.user.update({"name":"yuanhao"},{"$pop":{ "hobbies":-1} }) #5、按照條件刪除元素,:"$pull" 把符合條件的統統刪掉,而$pop只能從兩端刪 db.user.update({'addr.country':"China"},{"$pull":{ "hobbies":"read"} }, { "multi":true } )
刪:
#1、刪除多個中的第一個 db.user.deleteOne({ 'age': 8 }) #2、刪除國家為China的全部 db.user.deleteMany( {'addr.country': 'China'} ) #3、刪除全部 db.user.deleteMany({})
e、聚合
如果你有數據存儲在MongoDB中,你想做的可能就不僅僅是將數據提取出來那么簡單了;你可能希望對數據進行分析並加以利用。MongoDB提供了以下聚合工具: #1、聚合框架 #2、MapReduce(詳見MongoDB權威指南) #3、幾個簡單聚合命令:count、distinct和group。(詳見MongoDB權威指南) #聚合框架: 可以使用多個構件創建一個管道,上一個構件的結果傳給下一個構件。 這些構件包括(括號內為構件對應的操作符):篩選($match)、投射($project)、分組($group)、排序($sort)、限制($limit)、跳過($skip) 不同的管道操作符可以任意組合,重復使用
十四、爬蟲性能
a、背景知識
爬蟲的本質就是一個socket客戶端與服務端的通信過程,如果我們有多個url待爬取,只用一個線程且采用串行的方式執行,
那只能等待爬取一個結束后才能繼續下一個,效率會非常低。需要強調的是:對於單線程下串行N個任務,並不完全等同於低效,
如果這N個任務都是純計算的任務,那么該線程對cpu的利用率仍然會很高,之所以單線程下串行多個爬蟲任務低效,
是因為爬蟲任務是明顯的IO密集型程序。
b、同步、異步、回調機制
c、同步調用:即提交一個任務后就在原地等待任務結束,等到拿到任務的結果后再繼續下一行代碼,效率低下
十五、高性能
上述無論哪種解決方案其實沒有解決一個性能相關的問題:IO阻塞,無論是多進程還是多線程,在遇到IO阻塞時都會被操作系統強行剝奪走CPU的執行權限,
程序的執行效率因此就降低了下來。
解決這一問題的關鍵在於,我們自己從應用程序級別檢測IO阻塞然后切換到我們自己程序的其他任務執行,這樣把我們程序的IO降到最低,
我們的程序處於就緒態就會增多,以此來迷惑操作系統,操作系統便以為我們的程序是IO比較少的程序,從而會盡可能多的分配CPU給我們,這樣也就達到了提升程序執行效率的目的
a、在python3.3之后新增了asyncio模塊,可以幫我們檢測IO(只能是網絡IO),實現應用程序級別的切換
b、但asyncio模塊只能發tcp級別的請求,不能發http協議,因此,在我們需要發送http請求的時候,需要我們自定義http報頭
c、自定義http報頭多少有點麻煩,於是有了aiohttp模塊,專門幫我們封裝http報頭,然后我們還需要用asyncio檢測IO實現切換
d、此外,還可以將requests.get函數傳給asyncio,就能夠被檢測了
e、還有之前在協程時介紹的gevent模塊
f、封裝了gevent+requests模塊的grequests模塊
g、twisted:是一個網絡框架,其中一個功能是發送異步請求,檢測IO並自動切換
十六、scrapy
a、介紹
Scrapy一個開源和協作的框架,其最初是為了頁面抓取 (更確切來說, 網絡抓取 )所設計的,使用它可以以快速、簡單、可擴展的方式從網站中提取所需的數據。
但目前Scrapy的用途十分廣泛,可用於如數據挖掘、監測和自動化測試等領域,也可以應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。
Scrapy 是基於twisted框架開發而來,twisted是一個流行的事件驅動的python網絡框架。因此Scrapy使用了一種非阻塞(又名異步)的代碼來實現並發
b、安裝
#Windows平台 1、pip3 install wheel #安裝后,便支持通過wheel文件安裝軟件,wheel文件官網:https://www.lfd.uci.edu/~gohlke/pythonlibs 3、pip3 install lxml 4、pip3 install pyopenssl 5、下載並安裝pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/ 6、下載twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 7、執行pip3 install 下載目錄\Twisted-17.9.0-cp36-cp36m-win_amd64.whl 8、pip3 install scrapy #Linux平台 1、pip3 install scrapy
c、命令行工具
#1 查看幫助 scrapy -h scrapy <command> -h #2 有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不需要 Global commands: startproject #創建項目 genspider #創建爬蟲程序 settings #如果是在項目目錄下,則得到的是該項目的配置 runspider #運行一個獨立的python文件,不必創建項目 shell #scrapy shell url地址 在交互式調試,如選擇器規則正確與否 fetch #獨立於程單純地爬取一個頁面,可以拿到請求頭 view #下載完畢后直接彈出瀏覽器,以此可以分辨出哪些數據是ajax請求 version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本 Project-only commands: crawl #運行爬蟲,必須創建項目才行,確保配置文件中ROBOTSTXT_OBEY = False check #檢測項目中有無語法錯誤 list #列出項目中所包含的爬蟲名 edit #編輯器,一般不用 parse #scrapy parse url地址 --callback 回調函數 #以此可以驗證我們的回調函數是否正確 bench #scrapy bentch壓力測試 #3 官網鏈接 https://docs.scrapy.org/en/latest/topics/commands.html
d、文件說明:
- scrapy.cfg 項目的主配置信息,用來部署scrapy時使用,爬蟲相關的配置信息在settings.py文件中。
- items.py 設置數據存儲模板,用於結構化數據,如:Django的Model
- pipelines 數據處理行為,如:一般結構化的數據持久化
- settings.py 配置文件,如:遞歸的層數、並發數,延遲下載等。強調:配置文件的選項必須大寫否則視為無效,正確寫法USER_AGENT='xxxx'
- spiders 爬蟲目錄,如:創建文件,編寫爬蟲規則
十七、Spiders
a、介紹
#1、Spiders是由一系列類(定義了一個網址或一組網址將被爬取)組成,具體包括如何執行爬取任務並且如何從頁面中提取結構化的數據。 #2、換句話說,Spiders是你為了一個特定的網址或一組網址自定義爬取和解析頁面行為的地方
b、Spiders會循環做如下事情
#1、生成初始的Requests來爬取第一個URLS,並且標識一個回調函數 第一個請求定義在start_requests()方法內默認從start_urls列表中獲得url地址來生成Request請求,默認的回調函數是parse方法。回調函數在下載完成返回response時自動觸發 #2、在回調函數中,解析response並且返回值 返回值可以4種: 包含解析數據的字典 Item對象 新的Request對象(新的Requests也需要指定一個回調函數) 或者是可迭代對象(包含Items或Request) #3、在回調函數中解析頁面內容 通常使用Scrapy自帶的Selectors,但很明顯你也可以使用Beutifulsoup,lxml或其他你愛用啥用啥。 #4、最后,針對返回的Items對象將會被持久化到數據庫 通過Item Pipeline組件存到數據庫:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline) 或者導出到不同的文件(通過Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports) 復制代碼
e、Spiders總共提供了五種類
#1、scrapy.spiders.Spider #scrapy.Spider等同於scrapy.spiders.Spider #2、scrapy.spiders.CrawlSpider #3、scrapy.spiders.XMLFeedSpider #4、scrapy.spiders.CSVFeedSpider #5、scrapy.spiders.SitemapSpider
f、導入使用
g、class scrapy.spiders.spider
這是最簡單的spider類,任何其他的spider類都需要繼承它(包含你自己定義的)。
該類不提供任何特殊的功能,它僅提供了一個默認的start_requests方法默認從start_urls中讀取url地址發送requests請求,並且默認parse作為回調函數
十八、Selectors
#1 //與/ #2 text #3、extract與extract_first:從selector對象中解出內容 #4、屬性:xpath的屬性加前綴@ #4、嵌套查找 #5、設置默認值 #4、按照屬性查找 #5、按照屬性模糊查找 #6、正則表達式 #7、xpath相對路徑 #8、帶變量的xpath
