2017年2月10日

用python爬蟲爬科教館的歷屆科展資料

科展群傑廳是科教館將歷屆科展作品集結起來的資料庫型網站,什麼時候建置的?我只能確定是2008年以後,因為在那之前,我還特別去把生物科展的資料撈出來做成一個網頁連結。(見此文章:來客全國生物科展總匯吧

科展群傑廳建置之後,搜尋資料應該都很順利了吧?是有比以前好,但是還是有很多可以改進的地方。

首先是伺服器的處理速度,我用Google的PageSpeed Tools對科展群傑廳的分類連接進行分析,伺服器要花近10秒的時間,才能載入頁面。預設每個搜尋結果都是呈現10筆資料,每次換下一頁,又要花上近10秒的時間。如果搜尋時選擇全網站搜尋(全國科展和國際科展都一起找),搜尋時間又會花上更久,例如我搜尋「水蘊草」,則要花21.5秒才能呈現第一頁的結果。

第二是顯示模式,搜尋結果預設是顯示摘要的前100字,但是許多科展都沒有把摘要寫好,可能前五十個字都是廢話,因此還得再一一點入,才能看到比較完整的摘要。

每次讓學生上去查東西,都要花上很久的時間,實在太折騰人了,所以我決定了!
就寫一個python爬蟲去把頁面呈現的資訊全部都爬下來,像是標題、作者、摘要、連結...那些。(code就放在本篇文章最底下,是給我自己看的)

我把全部的科展資料都爬下來了,共計 11068 筆資料,存成csv檔放在雲端硬碟上,你可以直接用搜尋的功能找到想要查的關鍵字。你也可以下載之後,用excel開啟後進行自動篩選或搜尋,速度都會比你在網站上找要快。當你找到要細看的文章時,最後一個欄位是檔案連結位置,可以直接點選或利用瀏覽器下載。

在雲端硬碟上的歷屆科展彙整連結 科展資料彙整 (至2019)





以下是技術細節:

在科展群傑廳的資料庫裡,每一篇作品都會有一個sid變數(隨便點一篇科展的頁面,看網址列的最後一個變數就是)。所以我就是用sid從1去抓到最後一筆,目前sid用到最大是 13353,所以等到今年(2017)的科展作品也上線時,我就只要從13354繼續往上抓,就可以找到更新的資料了。
2020.0426更新
目前已加入了2019年的資料,最後一筆的sid是16132。下次抓再從16133開始抓



我使用的電腦環境是linux,所以我要搜尋科展資料的話,還可以用命令列來搜尋或是直接下載檔案。科展資料彙整的檔案,我存檔為data.csv

如果只是要搜尋關鍵字,可以用grep 去找關鍵字的科展作品
cat data.csv |grep "水蘊"

如果同時要找兩個關鍵字的交集,就只要把前一個關鍵字的搜尋結果,再用pipeline送給第二個grep
cat data.csv |grep "水蘊"|grep "聲音"

如果想要把搜尋出來的結果格式化成為比較容易閱讀的樣子,可把搜尋結果 pipeline到awk
cat data.csv |grep "水蘊"|awk -F"," '{print  "================\n",$2,"\t",$4,"\n================\n",$12,"\n\n" }
結果就會像這樣


搜尋關鍵字後想要一次下載全部有關的作品說明書,可以用awk 把wget的命令寫出來,再送給bash去執行。wget 的-O參數是指定下載的檔案名稱為科展作品的標題,所以這段小程式自動會將pdf檔改名為該科展的標題。
cat data.csv |grep "水蘊"|awk -F"," '{print "wget " $13 " -O " $2".pdf"}' |bash



python 3.0的code如下,之後使用上要特別修改的地方,就是用藍色標示的文字部份。


最底下淺灰色是舊網頁的抓法,已經淘汰。因為2020年時,網頁架構不一樣了,新的抓法如下
=====================


#!/usr/bin/env python
#-*- coding: utf-8 -*-
from bs4 import BeautifulSoup
#import urllib.request
from requests import get

def getData2(num):
    print(num)
 
 
    url_sid = num
    url_site = 'https://www.ntsec.edu.tw/Science-Content.aspx?sid=' 
    url = url_site + str(url_sid)


    page = get(url)
    #print(response.text)

    soup = BeautifulSoup(page.text, "html.parser")
    #s = soup.title.text


    #檢查是否error

    #如果Oops,代表沒有這份資料
    if 'Oops' in soup.p.text :
        print('no data')
        return
 

    #標題
    title = soup.find(class_ = 'pageTitle')
    title = title.get_text()
    #print(title + ',')
 
    line = str(num) + '\t'
    line = line + title + '\t'

    #id
    contents = ['ctl00_cphMainContent_ddScienceCategory',
                'ctl00_cphMainContent_ddScienceNumber',
                'ctl00_cphMainContent_ddSubject',
                'ctl00_cphMainContent_ddAward',
                'ctl00_cphMainContent_ddSchool',
                'ctl00_cphMainContent_ddTeacher',
                'ctl00_cphMainContent_ddAuthor',               
                'ctl00_cphMainContent_ddKeyWord',
                'ctl00_cphMainContent_dtNote']

    for content in contents:
        temp = soup.find(id = content)
        #國際科展有些會缺指導老師的資料,另外大多數科展會缺少備註欄位
        if temp != None :
            temp = temp.get_text()
            temp = temp.replace('\n',',')
            temp = temp.replace('\t',',')
        else:
            temp = ''
        #print(temp + ',')
        line = line + temp + '\t'



    #摘要
    abstract = soup.find(class_ = 'pagePanel__title')
    abstract = abstract.find_all_next('p')
    abstract = abstract[0]
    abstract = abstract.get_text()
    abstract = abstract.replace('\n','')

    #print(abstract + ',')
    line = line + abstract + '\t'
 
    #pdf link
    for link in soup.findAll("a", { "id" : "ctl00_cphMainContent_rptAtt_ctl00_aAttFile" }):
        link = "https://www.ntsec.edu.tw/" + link['href']
        #print(link)
        line = line + link
             
    line = line + "\n"
    return str(line)

file = open("data_16000-.csv","a")
for num in range(16000,17000,1):    
    line = getData2(num)
    #print(line)
    if line is not None:
        file.write(line)
file.close()

=====================

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from bs4 import BeautifulSoup
from urllib.request import urlopen


def getData(url2):
    print(url2)
    file = open("data-6000.csv","a")  
    line = str(url2) + ","
 
    url1 = 'http://science.ntsec.edu.tw/Science-Content.aspx?sid='  
    url = url1+str(url2)
    page = urlopen(url)

    soup = BeautifulSoup(page, "html.parser")
 

    #如果網頁標題有Error,代表沒有這份資料
    if "Error" in soup.title.text :
        return

    #印出網頁編碼
    #print(soup.original_encoding)

    #印出科展標題
    for science_title1 in soup.findAll("div", { "class" : "science_title1" }):
        title = science_title1.contents[0]
        title = title.replace(" ", "")
        title = title.replace(",", ",")
        title = title.replace(";", ";")
        title = title.replace("\"", "")
        title = title.replace(r"\n", "")
        print("標題",title)
        line = line + title + ","
        #如果科展標題是評語有"評語",代表不是科展
        if "評語" in title :
            return

    #印出基本資料的欄位名稱和內容
    scienceContent1 = soup.findAll("li", { "class" : "scienceContent1" })
    scienceContent1s = soup.findAll("span", { "class" : "scienceContent1s" })
 
    #如果科展類別的字數小於3,代表無此資料,則跳出副程式
    if len(scienceContent1s[0].contents) == 0 :
        return


 
    for dataName,data in zip(scienceContent1,scienceContent1s):
        dataName = dataName.contents[0][0:-3]
        dataName = dataName.replace(" ", "")
        dataName = dataName.replace(" ", "")
     
        #處理沒有得獎的科展
        if len(data) == 0:
            data = ""
        else:
            data = data.contents[0]
            data = data.replace(" ", "")
            #data = data.replace(r"\r", "")
            data = data.replace(",", ",")
            data = data.replace(";", ";")
        print(dataName,data)
        line = line + data + ","
    #印出摘要
    #abstractName = soup.findAll("div", { "class" : "science_title2" })
 
    for abstract in soup.findAll("div", { "class" : "scienceContent2_font" }):
        abstract = abstract.text
        abstract = abstract.replace("\n", "")
        abstract = abstract.replace(r"\r", "")
        abstract = abstract.replace(",", ",")
        abstract = abstract.replace(";", ";")
        abstract = abstract.replace('^M', "")
     
        print("摘要",abstract)
        line = line + abstract + ","
    print("===")

    for link in soup.findAll("a", { "id" : "ctl00_cphMainContent_aDownload" }):
        #如果連結有正確的話,才加入連結
        if "href" in str(link):
            link = link['href']
            link = "http://science.ntsec.edu.tw/"+link
            print("連結",link)
            line = line + link
    line = line + "\n"
 
    file.write(line)
    file.close()
for url2 in range(5832,6001,1):
    getData(url2)