請查收,一份讓你年薪突破20W的Python爬蟲筆記!

本次學習的教學視頻來自嵩天老師的網絡爬蟲教學,主要學習內容有requests\BeautifulSoup\scrapy\re,目前除了scrapy其他剛好看完。並搬運實現了一些小項目如58同城租房信息爬取、淘寶搜索商品項目,現將從爬蟲基本方法、實戰和遇到的問題三個方面進行總結。

1.基本方法

首先就是requests庫,是python最簡易實用的HTTP庫,是一個請求庫。主要方法如下,其中requests.request()方法最常用,用於構造請求,是其他幾種方法的總和。其餘方法如get()獲取HTML網頁,head()獲取網頁head標籤,post()\pu()t用於提交對應請求,patch()進行局部修改,delete()提交刪除請求。

請查收,一份讓你年薪突破20W的Python爬蟲筆記!


請查收,一份讓你年薪突破20W的Python爬蟲筆記!

著重介紹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對象,見下表。

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

補充幾段常用代碼。

(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即註釋。

標籤樹的遍歷方法有(上行遍歷、下行遍歷、平行遍歷)

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

此外可以用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次。*、+限定符都是貪婪的,因為它們會盡可能多的匹配文字,只有在它們的後面加上一個?就可以實現非貪婪或最小匹配。

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

請查收,一份讓你年薪突破20W的Python爬蟲筆記!

在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'編碼,避免出錯。


分享到:


相關文章: