iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 19
0
Modern Web

寫給PHP開發者的30堂網路爬蟲開發系列 第 19

Day 19:案例研究 2-1 實做課程查詢網站爬蟲-part3

  • 分享至 

  • xImage
  •  

前言

從昨天我們可以知道,該如何實做課程查詢爬蟲,並成功傳回第一頁課程清單,那接下來,如果是分頁的課程查詢結果清單呢?

在此篇文章中,會教該如何實做分頁課程查詢文章的爬蟲。

實做

首先,先如先前一樣,把之前所教的爬蟲開發環境跑起來。

docker run --name=php_crawler -d -it php_crawler bash

跑起來之後,用偏好的程式編輯器打開名字叫做「lab2-1-pagination.php」,並輸入下面的程式碼:

<?php

require_once __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

$publicCourses = 'https://infosys.nttu.edu.tw/n_CourseBase_Select/CourseListPublic.aspx';

$headers = [
    'Host' => 'infosys.nttu.edu.tw',
    'Connection' => 'keep-alive',
    'Cache-Control' => 'max-age=0',
    'Upgrade-Insecure-Requests' => '1',
    'Sec-Fetch-Mode' => 'navigate',
    'Sec-Fetch-User' => '?1',
    'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 application/signed-exchange;v=b3',
    'Sec-Fetch-Site' => 'none',
    'Referer' => 'https://infosys.nttu.edu.tw/',
    'Accept-Encoding' => 'gzip, deflate, br',
    'Accept-Language' => 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
    'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13',
];

$client = new Client(['cookies' => true]);
$response = $client->request('GET', $publicCourses, [
    'headers' => $headers,
]);

$publicCourseString = (string)$response->getBody();
$viewState = '__VIEWSTATE';
$eventValidation = '__EVENTVALIDATION';
$viewStateGenerator = '5D156DDA';

$crawler = new Crawler($publicCourseString);

$crawler
   ->filter('input[type="hidden"]')
   ->reduce(function (Crawler $node, $i) {
       global $viewState;
       global $eventValidation;
       if ($node->attr('name') === $viewState) {
           $viewState = $node->attr('value');
       }
       if ($node->attr('name') === $eventValidation) {
           $eventValidation = $node->attr('value');
       }
   });

$formParams = [
    'form_params' => [
        'ToolkitScriptManager1' => 'UpdatePanel1|Button3',
        'ToolkitScriptManager1_HiddenField' => '',
        'DropDownList1' => '1081',
        'DropDownList6' => '1',
        'DropDownList2' => '%',
        'DropDownList3' => '%',
        'DropDownList4' => '%',
        'TextBox9' => '',
        'DropDownList5' => '%',
        'DropDownList7' => '%',
        'TextBox1' => '',
        'DropDownList8' => '%',
        'TextBox6' => '0',
        'TextBox7' => '14',
        '__EVENTTARGET' => '',
        '__EVENTARGUMENT' => '',
        '__LASTFOCUS' => '',
        '__VIEWSTATE' => $viewState,
        '__VIEWSTATEGENERATOR' => $viewStateGenerator,
        '__SCROLLPOSITIONX' => '0',
        '__SCROLLPOSITIONY' => '0',
        '__EVENTVALIDATION' => $eventValidation,
        '__VIEWSTATEENCRYPTED' => '',
        '__ASYNCPOST' => 'false',
        'Button3' => '查詢',
    ],
    'headers' => [
        'Sec-Fetch-Mode: cors',
        'Origin: https://infosys.nttu.edu.tw',
        'Accept-Encoding: gzip, deflate, br',
        'Accept-Language: zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'X-Requested-With: XMLHttpRequest',
        'Connection: keep-alive',
        'X-MicrosoftAjax: Delta=true',
        'Accept: */*',
        'Cache-Control: no-cache',
        'Referer: https://infosys.nttu.edu.tw/n_CourseBase_Select/CourseListPublic.aspx',
        'Sec-Fetch-Site: same-origin',
        'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
    ],
];

$response = $client->request('POST', $publicCourses, $formParams);

$coursesString = (string)$response->getBody();

$viewState = '__VIEWSTATE';
$eventValidation = '__EVENTVALIDATION';

$crawler = new Crawler($coursesString);

$crawler
   ->filter('input[type="hidden"]')
   ->reduce(function (Crawler $node, $i) {
       global $viewState;
       global $eventValidation;
       if ($node->attr('name') === $viewState) {
           $viewState = $node->attr('value');
       }
       if ($node->attr('name') === $eventValidation) {
           $eventValidation = $node->attr('value');
       }
   });

$formParams['form_params']['ToolkitScriptManager1'] = 'UpdatePanel2|GridView1';
$formParams['form_params']['__EVENTTARGET'] = 'GridView1';
$formParams['form_params']['__EVENTARGUMENT'] = 'Page$5';
$formParams['form_params']['__VIEWSTATE'] = $viewState;
$formParams['form_params']['__EVENTVALIDATION'] = $eventValidation;
unset($formParams['form_params']['Button3']);

$response = $client->request('POST', $publicCourses, $formParams);

$coursesString = (string)$response->getBody();


$viewState = '__VIEWSTATE';
$eventValidation = '__EVENTVALIDATION';

$crawler = new Crawler($coursesString);

$crawler
   ->filter('input[type="hidden"]')
   ->reduce(function (Crawler $node, $i) {
       global $viewState;
       global $eventValidation;
       if ($node->attr('name') === $viewState) {
           $viewState = $node->attr('value');
       }
       if ($node->attr('name') === $eventValidation) {
           $eventValidation = $node->attr('value');
       }
   });

$formParams['form_params']['__EVENTARGUMENT'] = 'Page$12';
$formParams['form_params']['__VIEWSTATE'] = $viewState;
$formParams['form_params']['__EVENTVALIDATION'] = $eventValidation;

$response = $client->request('POST', $publicCourses, $formParams);

$coursesString = (string)$response->getBody();

var_dump($coursesString);

上述程式碼表示下列動作:

  • 首先,先用GET方法取得課程查詢頁面
  • 接著取得相關隱藏的驗證欄位的值
  • 利用POST方法並指定學年度與上述相關驗證欄位值一起送出得到第一頁的課程查詢列表結果
  • 接下來,利用上述第一頁頁面中的相關隱藏驗證值取得分頁第五頁的課程查詢結果
  • 利用第五頁得到頁面中的隱藏欄位值再去取得第12頁中的課程查詢結果

用下面指令將把此PHP檔丟到Docker container環境:

docker cp lab2-1-pagination.php     php_crawler:/root/

接著用下面方式執行此指令:

docker exec -it php_crawler php lab2-1-pagination.php

將此程式碼執行之後,會得到下面節錄的錯誤:

.......
<div>

	<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="AB827D4F" />
	<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEdAAbxmE99JhLisIrrBlSpleKvA3sa9CLAiY0NRgwF9EJQGh6kvJC1EopKW4ZDfj9Gj7oGHrYxvYrs5XDlrjyz+wVULvWz/wJ+1kADwg6S0w9SXo/Fg06KOWoBIRHuyh28DoVPLgf8rKyi7Ffc8EgW/ntaNx+wYA==" />
</div>
    <div>
        <span id="lblMsg">The error message:</span><br />
        <textarea name="txtMsg" rows="2" cols="20" id="txtMsg" class="input">
無效的回傳或回呼引數。已在組態中使用 &lt;pages enableEventValidation=&quot;true&quot;/&gt; 或在網頁中使用 &lt;%@ Page EnableEventValidation=&quot;true&quot; %&gt; 啟用事件驗證。基於安全性理由,這項功能驗證回傳或回呼引數是來自原本呈現它們的伺服器控制項。如果資料為有效並且是必須的,請使用 ClientScriptManager.RegisterForEventValidation 方法註冊回傳或回呼資料,以進行驗證。</textarea><br />
        <span id="lblStackTrace">The error stack trace:</span><br />
        <textarea name="txtStackTrace" rows="2" cols="20" id="txtStackTrace" class="input">
   於 System.Web.UI.ClientScriptManager.ValidateEvent(String uniqueId, String argument)
   於 System.Web.UI.Control.ValidateEvent(String uniqueID, String eventArgument)
   於 System.Web.UI.WebControls.GridView.RaisePostBackEvent(String eventArgument)
   於 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)</textarea>
        <br />
        <br />
        <span id="lblDesc">Feedback description:</span><br />
        <textarea name="txtDescription" rows="2" cols="20" id="txtDescription" class="input">
</textarea><br />
        <input type="submit" name="ButtonServerInfo" value="Server Stack" id="ButtonServerInfo" class="btn_mouseout" onmouseout="this.className=&#39;btn_mouseout&#39;" onmouseover="this.className=&#39;btn_mouseover&#39;" />
        <input type="submit" name="btnFeedback" value="Feedback" id="btnFeedback" class="btn_mouseout" onmouseout="this.className=&#39;btn_mouseout&#39;" onmouseover="this.className=&#39;btn_mouseover&#39;" /></div>
    </form>
</body>
</html>

上述錯誤會相關狀態驗證有錯誤,那為什麼?原因出在於分頁取得先後的問題,我們從課程查詢網站角度出發,我們在Chrome瀏覽器瀏覽這個頁面,截圖如下:

https://ithelp.ithome.com.tw/upload/images/20191004/20103975ssUj2zy0Bq.png

接著,按下「查詢」按鈕,會得到下面的截圖結果:

https://ithelp.ithome.com.tw/upload/images/20191004/201039756A9trRpFPG.png

可以注意到上述頁面最下面有一行的分頁:

https://ithelp.ithome.com.tw/upload/images/20191004/20103975aCAd0zprJw.png

頁面有111(第11頁是...按鈕),所以注意到了嘛?在使用者行為中,不會從這個頁面跳到第12頁,要按下第11頁的連結之後,才會跳轉到從第12頁開始的分頁選項列表,如下截圖:

https://ithelp.ithome.com.tw/upload/images/20191004/201039755IcsZuLjey.png

所以要到第12頁的課程查詢列表怎辦?照著使用者行為就是了,也就是說,先去請求第11頁的課程列表清單,接著拿到第11頁的相關隱藏驗證欄位值,再去請求第12頁的課程查詢結果。

所以把上述給'Page$5'改成'Page$11'即可。

接著再執行一遍程式,就會拿到下面分頁第12頁的結果了。

......
				<td style="width:60px;">必修</td><td style="width:100px;">[通識行政事務組]通識一</td><td style="width:100px;">英文</td><td style="width:100px;">UGE11B1AA004</td><td style="width:200px;">大一英文(一):基礎級</td><td style="width:60px;">
                                                <a onclick="window.open(&#39;CourseInfo1.aspx?id=29279&amp;yrsem=1081&#39;,&#39;_blank&#39;,&#39;toolbar=no, scrollbars=yes, resizable=no, top=0, left=0, width=600, height=450&#39;);" id="GridView1_ctl11_LinkButton3" class="button" href="javascript:__doPostBack(&#39;GridView1$ctl11$LinkButton3&#39;,&#39;&#39;)">V</a>
                                            </td><td style="width:60px;">2</td><td style="width:60px;">40</td><td style="width:60px;">10</td><td style="width:60px;">0</td><td style="width:60px;">32</td><td style="width:200px;">許孝芳</td><td style="width:100px;">11,12</td><td style="width:200px;">A101教室(62)</td><td style="width:100px;">&nbsp;</td><td style="width:100px;">&nbsp;</td><td style="width:300px;">一、初選階段限大一,師範學院;基礎級1,由通識中心分級匯入選課名單。二、加退選階段,開放大二、三、四學生下修;大一仍限師範學院。</td><td style="width:200px;">開放一、二、三、四年級選修,但大一部分,限師範學院學生選修。</td><td style="width:100px;">特殊課程</td>
......

對一下實際頁面第12頁最後一個課程名稱之截圖:

https://ithelp.ithome.com.tw/upload/images/20191004/20103975X4HfxoM0Oj.png

這樣就代表成功了。

結論

從今日的分頁課程查詢爬蟲實做來看,可以得到幾個重點:

  • 取得指定分頁要按照頁面上使用者行為操作才可以拿到預期的結果。
  • 分頁是每10頁會換一次,在分頁中合理範圍內是不會有錯誤的回應。比如說,拿第1頁的頁面去取第2頁到第11頁是不會有錯誤的。

到這裡,所有的課程查詢方式已經一個段落了,接下來,就是擷取課程內容的實做了,敬請期待!


上一篇
Day 18:案例研究 2-1 實做課程查詢網站爬蟲-part2
下一篇
Day 20:案例研究 2-1 擷取課程查詢網站內容
系列文
寫給PHP開發者的30堂網路爬蟲開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言