Photo by Andrey Tikhonovskiy on Unsplash

手把手python爬蟲教學(一): xpath

LUFOR129
14 min readSep 9, 2020

距離開學還有一個禮拜,現在真的是閒得發慌。雖然網路上已經有一堆爬蟲教學了,但是還是來複習一下之前學過的東西。本文介紹最基本的python requests+xpath教學。

一、安裝環境

推薦使用anaconda+jupyter 的方式安裝環境,詳細安裝說明可以參閱我之前寫過的文章前半段。

安裝好環境後,安裝下列package:

conda install -c anaconda requests
conda install -c anaconda lxml

執行看看如果沒有報錯,代表成功了

二、爬蟲

爬蟲就是,requests請求網頁>>等待接收>>接收>>分析網頁拿數據。我們可以利用xpath根據網頁的標籤來爬取所需要的數據。

import requests
from lxml import etree
# 請求並接收
response = requests.get("https://ithelp.ithome.com.tw/")
# response.status_code 回傳狀態 200,404,...
# response.content 回傳內容編碼 (純二進制)
# response.text 回傳Unicode型數據
# 解析內容 (轉為string)
content = response.content.decode()
# 把string轉為hmtl node tree,回傳根節點
html = etree.HTML(content)

拿到html根結點後,這裡建議可以在chrome 安裝一個插鍵: xpath helper

打開網頁開啟xpath helper後可以測試自己寫的xpath規則是否正確。

例如我要爬回答區第一個問題,打開F12可以看到他的html碼是在div class為"qa-list"下的第一個 h3 class為"qa-list_title"的a節點下。

title = html.xpath("//div[@class='qa-list'][1]//h3[@class='qa-list__title']/a/text()")

你可以透過xpath helper來驗證的的xpath規則

詳細xpath規則看這一篇,要注意xpath規則陣列自1開始算起(不是0)

三、爬蟲注意點

很多網頁你在爬時會發現瀏覽器顯示與程式爬到的數據不一樣,這可能有很多種原因: 網頁重新轉跳、網頁阻擋、JS動態網頁…之類的。

e.q. : 爬PTT就會被轉跳是否18歲,你可以利用webbrowser來查看是否程式爬取與瀏覽器上相同?

import webbrowser# 默認windows chrome路徑
chrome_path = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
# MacOS
# chrome_path = 'open -a /Applications/Google\ Chrome.app %s'
# Linux
# chrome_path = '/usr/bin/google-chrome %s'
def request_view(response):
request_url = response.url
base_url = '<head><base href="%s">'%(request_url)
base_url = base_url.encode()
content = response.content.replace(b'<head>',base_url)
temp_html = open('./temp.html','wb')
temp_html.write(content)
temp_html.close()
webbrowser.get(chrome_path).open_new_tab('./temp.html')

response = requests.get("https://www.ptt.cc/bbs/Gossiping/M.1599608225.A.AA4.html")
request_view(response)

破解方法請看下面第六點,解決Cookie!

四、圖片下載

可以先看一下圖片格式通常長什麼樣子?

可以我們主要是要拿src這個attribute

import requests
from lxml import etree
response = requests.get("https://ithelp.ithome.com.tw/questions/10200170")
html = etree.HTML(response.content)
# 可以拿到圖片url
imgs = html.xpath('//div[@class="markdown"]//img/@src')

每一個url可以用request來獲得二進位檔(content),並保存

import osimg_dir = "./imgs/"# 如果資料夾不存在就新增
if(os.path.isdir(img_dir) == False):
os.mkdir(img_dir)
#遍歷圖片 並 二進制寫入(wb)
for img in imgs:
file_name = img.split("/")[-1]
file_content = requests.get(img).content
with open(img_dir+file_name,"wb") as f:
f.write(file_content)
成功保存

五、爬取ajax的網站

有些網站是前後端分離的,簡單說也就是網頁裡面的資料是等待後端傳入後才呈現在畫面上而非寫死在網頁上直接傳給你。

舉個例子,例如591房屋網,如果你直接複製他們的網址去爬:

# 591會擋header非網頁,所以要模擬一下(user-agent)response = requests.get(
"https://newhouse.591.com.tw/housing-list.html?&rid=1",
headers={
'user-agent': 'Mozilla/5.0 (Macintosh Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
)
request_view(response)

你會發現結果如下:

一個建案都找不到,這是因為591是前後端分離,建案內容是載入網頁後才會後續載入的。按下F12 / Network /XHR 然後ctrl+R 重跑一遍,你會發現建案資料都躲在search?rid=1&sid=&page=1 的data底下。

查看request 請求頭,原來是要對requests URL做get阿!

rid經過測試是縣市,page是第幾頁,而回傳的是json,所以我們就可以很輕易的獲取資料了。

import jsonresponse = requests.get(
"https://newhouse.591.com.tw/home/housing/search?rid=1&sid=&page=1",
headers={
'user-agent': 'Mozilla/5.0 (Macintosh Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'
}
)
content = response.content.decode()# str to json
json_data = json.loads(content)
# 印出第一個
print(json_data["data"]["items"][0])
結果

六、爬取含有Cookie網站

如果是那種PTT打開會詢問是否18歲的網站呢?很多網站都是會根據Cookie來決定你是否可以進入。你試試看用無痕打開PTT會不會跳是否18

點擊確定超過18歲後會出現一個request post向over18

而這個post帶有參數from 和 yes

我們可以使用session來讓requests之間連續。我們要做的事是讓session先request post 到over18一次再去request get 我們原本要的網站。

import requestss = requests.Session()
data = {
'from':'/bbs/Gossiping/index.html',
'yes':'yes'
}
# 建立session 與 cookie
pass_18 = s.post("https://www.ptt.cc/ask/over18",data=data)
response = s.get("https://www.ptt.cc/bbs/Gossiping/index.html")

如此一來就能正確的爬到了。

七、爬取含有x-csrf-token的網站

有些網站在進行登入時會要求x-csrf-token他會針對cookie做檢查,例如dcard就是。那麼該如何用requests登入dcard發發廢文呢?

打開https://www.dcard.tw/signup頁面,並實際用帳號登入看看,會發現在登入的header包含了一個對dcard sessions api的post。

拉到下面查看requests header,查看cookie與x-csrf-token。把他記起來。

再往下拉看到post 傳入的參數

那麼其實做法與上面的session很像,只是傳入的參數變多了

import requests
import re
import json
s = requests.Session()data = {
'email': "自己的帳號",
'password': "自己的密碼",
}
headers = {
'x-csrf-token':'貼上你的csrf token',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36',
'cookie': '把cookie整段貼過來,太長了不想寫',
'content-type': 'application/json'
}
# 傳入data與header
login_dcard = s.post("https://www.dcard.tw/service/sessions",data=json.dumps(data),headers=headers)
# 查看是否正確登入打開個人頁面
response = s.get("https://www.dcard.tw/my/collections")
request_view(response)
恩,看來有好好登入呢! (我的頭像是蕾賽,蕾賽我婆)

接下來來試試看發個廢文,先自己發個廢文看看api。會出現一個post的request,看起來發在哪個版跟request url相關 (我是發在廢文版 whysoserious)

看一下參數

那來發廢文吧!

post_payload={
'anonymous':'true',
'content':"python 發發廢文",
'layout':'classic',
'thumbnailIndices':[],
'title': "不知道發廢文是什麼感覺",
'topics':[],
'withNickname':'false'
}
# 同上,自己發一篇廢文看看你的csrf與cookie
headers = {
'x-csrf-token':'貼上你的csrf token',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36',
'cookie': '把cookie整段貼過來,太長了不想寫',
'content-type': 'application/json'
}
post_dcard = s.post('https://www.dcard.tw/service/api/v2/forums/whysoserious/posts',data=json.dumps(post_payload),headers=headers)

然後就能成功自動發一篇文了,發完記得刪掉!!!

以上講解僅供學術交流,請不要到Dcard發大量文章洗版。

--

--