1. 程式人生 > >解決貓眼網反爬蟲策略的爬蟲

解決貓眼網反爬蟲策略的爬蟲

專案程式碼:Github

[目錄]


一.引入問題

1

可以看到,貓眼網電影評分,票房等的資料在響應的html中並不是直接提供給你的。這裡的xefcf,xef87等資料,是以‘特殊符號’的形式顯示出來的。

2

可以發現這裡請求了一個woff字型檔案,而xefcf,xef87等資料的規則就是在這其中的。所以我們只需要在請求這個網頁的同時,截去這部分style,然後獲得woff地址,將它下載到本地,進行解析,就可以實現對評分的解密了。

但是再來看下面,當同一部電影的頁面重新整理時。

3
4

這裡的評分編碼改變了,下面請求的woff的url也改變了。所以每次請求電影頁面使用的woff都不是同一個。

在這種情況下,如何才可以實現評分的爬取呢?

二.分步實現

1.頁面爬取

第一步我們需要先將整個html頁面獲取,其中重要的資料就是評分所在的那個span,還有woff的url所在的那個style.

這裡使用requests獲取網頁內容,用BeautifulSoup進行關鍵內容解析.

# 請求頭設定
header = {
    'Accept': '*/*;',
    'Connection': 'keep-alive',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'Host': 'maoyan.com',
    'Referer': 'http://maoyan.com/',
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'
}
def web(url):
    db_data = requests.get(url, headers=header)
    # 這裡直接將其編碼之前部分替換掉,防止之後使用轉義
    soup = BeautifulSoup(db_data.text.replace("&#x",""), 'lxml')
    titles = soup.select(
        'body > div.banner > div > div.celeInfo-right.clearfix > div.movie-stats-container > div > div > span > span')
    wotfs = soup.select('head > style')

    wotflist = str(wotfs[0]).split('\n')
    maoyanwotf就是當前頁面所使用的woff地址了。

     = wotflist[5].replace(' ','').replace('url(\'//','').replace('format(\'woff\');','').replace('\')','')

此時titles就是一個獲取了評分span的list,當然它只有一個元素。

maoyanwotf就是當前頁面所使用的woff地址了。

2.woff下載

上一步我們得到了maoyanwotf,其中是當前頁面所使用的woff地址,所以接下來就要對這個url進行下載,儲存到本地。

# 下載請求電影頁面的woff字型到本地
def downfont(maoyanwotf):
    r = requests.get('http://'+maoyanwotf)
    with open("demo.woff", "wb") as code:
        code.write(r.content)
    font = TTFont("demo.woff")
    font.saveXML('to.xml')

這樣就下載到了這次請求頁面所用的woff了,儲存為demo.woff。

3.字型解析規則

那麼這個to.xml有什麼用呢?

這就是我們在每次重新整理都會改變woff的情況下,實現爬取的關鍵所在了。

font.saveXML('to.xml')就是將TTFont開啟的woff轉換為xml的形式,在xml中我們可以直接找到字型的相關資料,比如一個字元的筆畫資訊等。

來看一個字型xml檔案的重要內容。完整內容參考:to.xml

<!--GlyphOrder 這個標籤中就是這個字型xml包含的字元種類了,glyph00000和x這兩個不用考慮 -->
<GlyphOrder>
  <GlyphID id="0" name="glyph00000"/>
  <GlyphID id="1" name="x"/>
  <GlyphID id="2" name="uniEE29"/>
  <GlyphID id="3" name="uniE6A3"/>
  ....
  <GlyphID id="9" name="uniE47E"/>
  <GlyphID id="10" name="uniE9BA"/>
  <GlyphID id="11" name="uniEEB4"/>
</GlyphOrder>
<!--glyf 這個標籤中是這些字型的具體座標畫法,一個TTGlyph對應一個字型,而其中contour標籤的座標資料,就是唯一確定這個字型是什麼的最好的確定方法-->
<glyf>
  <TTGlyph name="glyph00000"/><!-- contains no outline data -->
  <TTGlyph name="uniE47E" xMin="0" yMin="-13" xMax="511" yMax="719">
    <contour>
      <pt x="130" y="201" on="1"/>
      ...
      <pt x="42" y="189" on="1"/>
    </contour>
    <instructions/>
  </TTGlyph>
  ...
  ...
  <TTGlyph name="uniE6A3" xMin="0" yMin="0" xMax="503" yMax="719">
    <contour>
      <pt x="503" y="84" on="1"/>
        ...
      <pt x="152" y="84" on="1"/>
    </contour>
    <instructions/>
  </TTGlyph>

</glyf>
GlyphOrder 這個標籤中就是這個字型xml包含的字元種類了,glyph00000和x這兩個不用考慮。
glyf 這個標籤中是這些字型的具體座標畫法,一個TTGlyph對應一個字型,而其中contour標籤的座標資料,就是唯一確定這個字型是什麼的最好的確定方法。

所以可以得出結論,不管每次請求的woff怎麼變,它裡面的字元的contour標籤內的座標畫法是不會改變的。

既然如此,我們只需要一個模板,在這個模板中,我們先手動解碼一個充當模板的woff。

如何製作這樣一個模板呢?

我們先來找一個woff和woff生成的xml,woff可以用字型檢視器High-Logic FontCreator開啟。效果如下。

6

之後我們將模板xml裡面的glyf中TTGlyph標籤的name屬性與開啟的woff對應比較,得到這個數值列表(我這裡woff和xml用的不是同一個,所以不要用這個比較!!!)

5

之後我們就可以根據glyf中TTGlyph的順序得到解碼數值的順序,之所以要一一對應,是為了方便之後用下標索引。

  num = [8,6,2,1,4,3,0,9,5,7]  #這個值是直接硬編碼進去的。
  data = []
  # 此處以解析xml的方式得到這個模板xml裡面contour標籤的內容。
  xmlfilepath_temp = os.path.abspath("temp.xml")
  domobj_temp = xmldom.parse(xmlfilepath_temp)
  elementobj_temp = domobj_temp.documentElement
  subElementObj = elementobj_temp.getElementsByTagName("TTGlyph")
  for i in range(len(subElementObj)):
    rereobj = re.compile(r"name=\"(.*)\"")
    find_list = rereobj.findall(str(subElementObj[i].toprettyxml()))
    data.append(str(subElementObj[i].toprettyxml()).replace(find_list[0],'').replace("\n",''))

最後data裡面是glyf中TTGlyph按順序的內容,其中當然就是contour了,而它的下表與num中一一對應,也就是說,這時候給你一個contour的座標內容,那就可以在data中查詢它的對應下標,根據這個下標找到num中下標的元素,這個元素就是解碼後的值了!!!

獲得了模板,我們接下來就可以將請求這次頁面時獲得的woff轉化為xml進行對應了。

font = TTFont("demo.woff")
font.saveXML('to.xml')

#本次請求下載的字型的contour
new_font = []
xmlfilepath_find = os.path.abspath("to.xml")
domobj_find = xmldom.parse(xmlfilepath_find)
elementobj_find = domobj_find.documentElement
tunicode = elementobj_find.getElementsByTagName("TTGlyph")
for i in range(len(tunicode)):
    th = tunicode[i].toprettyxml()
    report = re.compile(r"name=\"(.*)\"")
    find_this = report.findall(th)
    get_code = th.replace(find_this[0], '').replace("\n", '')
    for j in range(len(data)):
        if not cmp(get_code,data[j]):
            new_font.append(num[j])

其中get_code就是這次頁面時獲得的字型資料的list元素之一了,通過迴圈遍歷,最終得到了一個本次xml的對應數字的list : new_font 。

接下來獲得一個編碼與具體數字對應的font_list

    font = TTFont("demo.woff")
    font_list = font.getGlyphNames()
    font_list.remove('glyph00000')
    font_list.remove('x')

此時,有了new_font中的具體數字和font_list中的編碼值就可以完全解碼這個頁面中因為woff編碼反爬蟲的評分了。

# 匹配
star_woff = re.findall(re.compile(r">(.*)<"), str(titles[0]))[0].replace(';','').split('.')  ;titles[0]是在第一步網頁解析時獲得的評分所在span
for i in star_woff:
    getthis = i.upper()
    for j in range(len(font_list)):
        if not cmp(getthis,font_list[j].replace("uni","")):
            print(new_font[j])

這樣就會輸出評分的整數部分和小數部分了。雖然這裡只是輸出了這兩個數字,但是反爬蟲評分,票房,人數等資料均可以使用前一步的new_font和font_list進行處理,就不再加以闡述了。

7