本次學習的教學視頻來自嵩天老師的網絡爬蟲教學,主要學習內容有requests\BeautifulSoup\scrapy\re,目前除了scrapy其他剛好看完。並搬運實現了一些小項目如58同城租房信息爬取、淘寶搜索商品項目,現將從爬蟲基本方法、實戰和遇到的問題三個方面進行總結。
1.基本方法
首先就是requests庫,是python最簡易實用的HTTP庫,是一個請求庫。主要方法如下,其中requests.request()方法最常用,用於構造請求,是其他幾種方法的總和。其餘方法如get()獲取HTML網頁,head()獲取網頁head標籤,post()\pu()t用於提交對應請求,patch()進行局部修改,delete()提交刪除請求。
著重介紹request.get()方法,requests.get(url, params=None,**kwargs)
其中url為頁面鏈接,params為額外參數,字典格式,**kwargs包含了12個控制訪問的參數。(params\data\json\headers\cookies\auth\files\timeout\proxies\allow_redirects\stream\verify\cert)
通常我們使用get()方法獲取頁面的內容。
接著介紹請求得到的Response對象,見下表。
補充幾段常用代碼。
(1)爬取京東商品
<code>import requests url = "https://item.jd.com/2967929.html" try: r = requests.get(url) r.raise_for_status() #如果發送了錯誤請求,可以拋出異常 r.encoding = r.apparent_encoding #把文本內容的編碼格式傳遞給頭文件編碼格式 print(r.text[:1000]) except: print("爬取失敗!") /<code>
(2)爬取亞馬遜,需要修改headers字段,模擬請求
<code>import requests url="https://www.amazon.cn/gp/product/B01M8L5Z3Y" try: kv = {'user-agent':'Mozilla/5.0'} #模擬請求頭 r=requests.get(url,headers=kv) r.raise_for_status() r.encoding=r.apparent_encoding print(r.status_code) print(r.text[:1000]) except: print("爬取失敗") /<code>
(3)百度搜索關鍵詞提交-params提交關鍵詞
<code>import requests url="http://www.baidu.com/s" try: kv={'wd':'Python'} r=requests.get(url,params=kv) print(r.request.url) r.raise_for_status() print(len(r.text)) print(r.text[500:5000]) except: print("爬取失敗") /<code>
(4)圖片爬取存儲
<code>import requests import os url="http://tc.sinaimg.cn/maxwidth.800/tc.service.weibo.com/p3_pstatp_com/6da229b421faf86ca9ba406190b6f06e.jpg" root="D://pics//" path=root + url.split('/')[-1] try: if not os.path.exists(root): os.mkdir(root) if not os.path.exists(path): r = requests.get(url) with open(path, 'wb') as f: f.write(r.content) #r.content為圖片 f.close() print("文件保存成功") else: print("文件已存在") except: print("爬取失敗") /<code>
下面介紹BeautifulSoup庫,用於對網頁內容進行解析。
BeautifulSoup(mk, 'html.parser'),可以用html.parser\lxml\xml\html5lib作為解析器,這裡選取html.parser。
元素主要有Tag\Name\Attributes\NavigableString\Comment。其中Tag使用方法如(soup.a),attrs使用如(a.attrs['class']),Navigable(tag.string)為非屬性字符串,comment即註釋。
標籤樹的遍歷方法有(上行遍歷、下行遍歷、平行遍歷)
此外可以用soup.prettify()輸出有層次感的段落。
信息提取方法如下:常用find_all,具體對標籤搜索有soup.find_all('a'),對屬性搜索有soup.find_all('p',class='course'),對字符串搜索有soup.find_all(string='...'),配合正則表達式檢索有soup.find_all(re.compile('link'))。
<code> find() 搜索且返回一個結果,字符串類型 find_parents() 在先輩節點中搜索,返回一個列表類型 find_parent() 在先輩節點中返回一個結果,字符串類型 find_next_siblings() 在後續平行節點搜索,返回列表類型 find_next_sibling() find_previous_siblings() find_previous_sibling() 在前序平行節點中返回一個結果,字符串類型 find_all(name,attrs,recursive,string,**kwargs) 返回一個列表類型,存儲查找的結果 參數: name:對標籤名稱的檢索字符串,可以使用列表查找多個標籤,find_all(true)所有標籤 attrs:對標籤屬性值的檢索字符串,可標註屬性檢索 例如find_all('a','href') recursive:是否對子孫所有節點搜索,默認值為true,false則值查找當前節點兒子的信息 string: <>>中字符串區域的檢索字符串 /<code>
最後介紹Re正則表達式庫。
正則表達式限定符如下:
貪婪匹配指匹配的數據無限多,所謂的的非貪婪指的是匹配的次數有限多。一般情況下,非貪婪只要匹配1次。*、+限定符都是貪婪的,因為它們會盡可能多的匹配文字,只有在它們的後面加上一個?就可以實現非貪婪或最小匹配。
在re庫中一般使用raw string類型即r'text'。其中遇到特殊字符需要 \ 加以轉義。
方法如下
<code> re.search(pattern,string,flag=0)在一個字符串中搜索匹配正則表達式的第一個位置,返回match對象 re.match() 在一個字符串的開始位置起匹配正則表達式,返回match對象 注意match為空 re.findall()搜索字符串,一列表類型返回全部能匹配的子串 re.split()將一個字符串按照正則表達式匹配結果進行分割,返回列表類型 re.finditer() 搜索字符串,返回一個匹配結果的迭代類型,每個迭代元素是match對象 re.sub()在一個字符串中替換所有匹配正則表達式的子串,返回替換後的字符串 re.compile(pattern,flags) 將正則表達式的字符串形式編譯成正則表達式對象 /<code>
flag = 0中有三種選擇類型,re.I忽略大小寫、re.M從每行開始匹配、re.S匹配所有字符。
以上是函數式用法,此外還有面向對象用法。
<code>pat = re.compile('') pat.search(text) /<code>
最後介紹match對象的屬性和方法,見下。
<code> 1、屬性 1)string 待匹配文本 2)re 匹配時使用的pattern對象(正則表達式) 3)pos 正則表達式搜索文本的開始位置 4)endpos 正則表達式搜索文本的結束為止 2、方法 1).group(0) 獲得匹配後的字符串 2).start() 匹配字符串在原始字符串的開始位置 3).end() 匹配字符串在原始字符串的結束位置 4).span() 返回(.start(),.end())元組類型 /<code>
2.實戰演練
主要選取了淘寶商品搜索和58同城租房兩個實例,鏈接分別為‘https://blog.csdn.net/u014135206/article/details/103216129?depth_1-utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromBaidu-8&utm_source=distribute.pc_relevant_right.none-task-blog-BlogCommendFromBaidu-8‘ 'https://cloud.tencent.com/developer/article/1611414'
淘寶搜索
<code>import requests import re def getHTMLText(url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0' } #cookies在元素審查,網絡裡面刷新,找請求頭下面的Cookie usercookies = '' #這裡需要使用客戶端的淘寶登錄cookies cookies = {} for a in usercookies.split(';'): name, value = a.strip().split('=', 1) cookies[name] = value print(cookies) try: r = requests.get(url, headers=headers, cookies=cookies, timeout=60) r.raise_for_status() #如果有錯誤返回異常 print(r.status_code) #打印狀態碼 r.encoding = r.apparent_encoding return r.text except: return 'failed' def parsePage(ilt, html): try: plt = re.findall(r'"view_price"\:"[\d\.]*"', html) tlt = re.findall(r'"raw_title"\:".*?"', html) for i in range(len(plt)): price = eval(plt[i].split(':')[1]) # 意義是進行分割其冒號 title = eval(tlt[i].split(':')[1]) ilt.append([price, title]) except: print("") def printGoodsList(ilt): tplt = "{:4}\t{:8}\t{:16}" print(tplt.format("序號", "價格", "商品名稱")) # 輸出信息 count = 0 for g in ilt: count = count + 1 print(tplt.format(count, g[0], g[1])) def main(): goods = '足球' depth = 3 start_url = 'http://s.taobao.com/search?q={}&s='.format(goods) # 找到起始頁的url鏈接 infoList = [] for i in range(depth): # 進行循環爬去每一頁 try: url = start_url + str(44 * i) html = getHTMLText(url) parsePage(infoList, html) except: continue printGoodsList(infoList) main() /<code>
58同城爬取租房,這部分代碼較多,選取重要內容展示。
1.加密字體的解碼
<code># 獲取字體文件並轉換為xml文件 def get_font(page_url, page_num, proxies): response = requests.get(url=page_url, headers=headers, proxies=proxies) # 匹配 base64 編碼的加密字體字符串 base64_string = response.text.split("base64,")[1].split("'")[0].strip() # print(base64_string) # 將 base64 編碼的字體字符串解碼成二進制編碼 bin_data = base64.decodebytes(base64_string.encode()) # 保存為字體文件 with open('58font.woff', 'wb') as f: f.write(bin_data) print('第' + str(page_num) + '次訪問網頁,字體文件保存成功!') # 獲取字體文件,將其轉換為xml文件 font = TTFont('58font.woff') font.saveXML('58font.xml') print('已成功將字體文件轉換為xml文件!') return response.text # 將加密字體編碼與真實字體進行匹配 def find_font(): # 以glyph開頭的編碼對應的數字 glyph_list = { 'glyph00001': '0', 'glyph00002': '1', 'glyph00003': '2', 'glyph00004': '3', 'glyph00005': '4', 'glyph00006': '5', 'glyph00007': '6', 'glyph00008': '7', 'glyph00009': '8', 'glyph00010': '9' } # 十個加密字體編碼 unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5'] num_list = [] # 利用xpath語法匹配xml文件內容 font_data = etree.parse('./58font.xml') for unicode in unicode_list: # 依次循環查找xml文件裡code對應的name result = font_data.xpath("//cmap//map[@code='{}']/@name".format(unicode))[0] # print(result) # 循環字典的key,如果code對應的name與字典的key相同,則得到key對應的value for key in glyph_list.keys(): if key == result: num_list.append(glyph_list[key]) print('已成功找到編碼所對應的數字!') # print(num_list) # 返回value列表 return num_list # 替換掉網頁中所有的加密字體編碼 def replace_font(num, page_response): # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5 result = page_response.replace('鑶', num[0]).replace('閏', num[1]).replace('餼', num[2]).replace('驋', num[3]).replace('鵂', num[4]).replace('麣', num[5]).replace('齤', num[6]).replace('龒', num[7]).replace('龤', num[8]).replace('龥', num[9]) print('已成功將所有加密字體替換!') return result /<code>
2.租房信息爬取
<code># 提取租房信息 def parse_pages(pages): num = 0 soup = BeautifulSoup(pages, 'lxml') # 查找到包含所有租房的li標籤 all_house = soup.find_all('li', class_='house-cell') for house in all_house: # 標題 # title = house.find('a', class_='strongbox').text.strip() # print(title) # 價格 price = house.find('div', class_='money').text.strip() price = str(price) print(price) # 戶型和麵積 layout = house.find('p', class_='room').text.replace(' ', '') layout = str(layout) print(layout) # 樓盤和地址 address = house.find('p', class_='infor').text.replace(' ', '').replace('\n', '') address = str(address) print(address) num += 1 print('第' + str(num) + '條數據爬取完畢,暫停3秒!') time.sleep(3) with open('58.txt', 'a+', encoding='utf-8') as f: #這裡需encoding編碼為utf-8,因網絡讀取的文本和寫入的文本編碼格式不一;a+繼續在文本底部追加內容。 f.write(price + '\t' + layout + '\t' + address + '\n') /<code>
3.由於58會封禁爬蟲IP地址,還需要爬取ip進行切換。
<code>def getiplists(page_num): #爬取ip地址存到列表,爬取pages頁 proxy_list = [] for page in range(1, page_num): url = " "+str(page) r = requests.get(url, headers=headers) soup = BeautifulSoup(r.text, 'lxml') ips = soup.findAll('tr') for x in range(5, len(ips)): ip = ips[x] tds = ip.findAll("td") #找到td標籤 ip_temp = 'http://'+tds[1].contents[0]+":"+tds[2].contents[0] #.contents找到子節點,tag之間的navigable也構成了節點 proxy_list.append(ip_temp) proxy_list = set(proxy_list) #去重 proxy_list = list(proxy_list) print('已爬取到'+ str(len(proxy_list)) + '個ip地址') return proxy_list 通過更新proxies,作為參數更新到requests.get()中,可以一直刷新IP地址。 proxies = { 'http': item, 'https': item, } /<code>
3.經驗總結
期間遇到問題彙總如下:
1.大多數網站都需要模擬請求頭,user-agent。
2.淘寶需要模擬cookies登陸,cookies信息可以在檢查元素中找到。
3.這種方法只能爬取靜態的網頁,對於數據寫入javascript的動態網頁,還需要新的知識。
4.爬取過程中容易被封IP,需要在IP代理網站爬取IP地址,不斷刷新IP地址。在get()方法中增加proxies參數即可。
5.58的價格字符串採用的加密的方式顯示,需要解碼。
6.寫入文本時要用encoding='utf-8'編碼,避免出錯。