1. 程式人生 > >爬取中國知網CNKI的遇到的坑與技術總結

爬取中國知網CNKI的遇到的坑與技術總結

參考部落格及資料

最近要寫一個數據分析的專案,需要根據關鍵詞爬取近十年期刊的主要資訊,記錄一下爬取過程中遇到的問題

分析

cnki算是對爬蟲作了一定抵禦,我們要爬取學術論文詳情頁的主題,摘要等資訊,主要步驟和其他網站的爬取大致相似:一是要根據關鍵詞搜尋到列表頁;二是要從列表頁請求得到詳情頁,從詳情頁取得我們所要的資訊。

  • 入口頁面:[kns.cnki.net/kns/brief/d…]

  • 搜尋後,js動態渲染的請求列表頁面:[kns.cnki.net/kns/brief/b…

    ..] 這裡我們開啟Developer Tools觀察請求頭和引數

    這裡的關鍵資訊: ① 請求引數,我們觀察到請求的關鍵詞在欄位KeyValue中(GET請求); ② cookiereferer:如果沒有在請求頭部加入referer,我們將無法開啟這個列表頁,如果沒有在頭部中加入cookie,我們請求後得到的頁面內容是不完整的!注意:iframe列表詳情頁只有從入口頁面請求渲染後才能得到,這步請求不能省略!

  • 從列表頁的連結中解析得到詳情頁[kns.cnki.net/KCMS/detail…...]

    我們繼續開啟Developer Tools觀察網頁的HTML中跳轉到詳情頁的連結
    這裡我們發現,連結的地址和我們最終得到的地址是不同的!是因為網頁重定向了!

  • 詳情頁,這裡就只要解析網頁即可,我們通過xpath可以很容易得到題目,作者,關鍵詞,摘要等資訊

Scrapy實戰

  1. 如何設定cookie:

    • settings中設定COOKIES_ENABLED=True
    • http請求參考Scrapy - how to manage cookies/sessions
    • 補充:cookiejar模組的主要作用是提供可儲存的cookie物件,可以捕獲cookie並在後續連線請求時重新發送,實現模擬登入功能。在scrapy中可以在請求是傳入meta引數設定,根據不同會話記錄對應的cookie:
  2. 如何請求入口頁:(CJFQ代表期刊,可根據需求更改)

        data = {
            "txt_1_sel": "SU$%=|",
            "txt_1_value1": self.key_word,
            "txt_1_special1": "%",
            "PageName": "ASP.brief_default_result_aspx",
            "ConfigFile": "SCDBINDEX.xml",
            "dbPrefix": "CJFQ",
            "db_opt": "CJFQ",
            "singleDB": "CJFQ",
            "db_codes": "CJFQ",
            "his": 0,
            "formDefaultResult": "",
            "ua": "1.11",
            "__": time.strftime('%a %b %d %Y %H:%M:%S') + ' GMT+0800 (中國標準時間)'
        }
        query_string = parse.urlencode(data)
        yield Request(url=self.home_url+query_string,
                      headers={"Referer": self.cur_referer},
                      cookies={CookieJar: 1},
                      callback=self.parse)
複製程式碼
  1. 如何請求列表頁
    def parse(self, response):
        data = {
            'pagename': 'ASP.brief_default_result_aspx',
            'dbPrefix': 'CJFQ',
            'dbCatalog': '中國學術期刊網路出版總庫',
            'ConfigFile': 'SCDBINDEX.xml',
            'research': 'off',
            't': int(time.time()),
            'keyValue': self.key_word,
            'S': '1',
            "recordsperpage": 50,
            # 'sorttype': ""
        }
        query_string = parse.urlencode(data)
        url = self.list_url + '?' + query_string
        yield Request(url=url,
                      headers={"Referer": self.cur_referer},
                      callback=self.parse_list_first)
複製程式碼
  1. 如何解析列表頁
    • 獲得列表總頁數:
    response.xpath('//span[@class="countPageMark"]/text()').extract_first()
    max_page = int(page_link.split("/")[1])
    複製程式碼
    • 請求每個列表頁
                    data = {
                    "curpage": page_num,#迴圈更改
                    "RecordsPerPage": 50,
                    "QueryID": 0,
                    "ID":"",
                    "turnpage": 1,
                    "tpagemode": "L",
                    "dbPrefix": "CJFQ",
                    "Fields":"",
                    "DisplayMode": "listmode",
                    "PageName": "ASP.brief_default_result_aspx",
                    "isinEn": 1
                }
    複製程式碼
    • 解析列表頁(這裡如果結果為空,請檢查你是否正確設定了cookie)
        tr_node = response.xpath("//tr[@bgcolor='#f6f7fb']|//tr[@bgcolor='#ffffff']")
        for item in tr_node:
            paper_link = item.xpath("td/a[@class='fz14']/@href").extract_first()
    複製程式碼
  2. 如何解析詳情頁(只是一個示例,有很多種解析方法)
    title = response.xpath('//*[@id="mainArea"]/div[@class="wxmain"]/div[@class="wxTitle"]/h2/text()').extract()
    author = response.xpath('//*[@id="mainArea"]/div[@class="wxmain"]/div[@class="wxTitle"]/div[@class="author"]/span/a/text()').extract()
    abstract = response.xpath('//*[@id="ChDivSummary"]/text()').extract()
    keywords = response.xpath('//*[@id="catalog_KEYWORD"]/following-sibling::*/text()').extract()
    複製程式碼

歡迎fork我的Github專案

2018.12.15 更新

上面專案在爬取數量不多的專案時不會報錯,但是我嘗試爬取20w資料量的論文時,發現每次只有1000多條資料,觀察發現請求轉到vericode.aspx頁,即驗證碼頁面。因為我不怎麼會處理驗證碼啦,所以果斷放棄,使用手機知網的介面wap.cnki.net/touch/web,發現真的so easy!

  1. 請求列表頁
    第一步還是要觀察請求,開啟DevTool
    可以簡單的構造第一個請求(GET):
    def start_requests(self):
        data = {
            "kw": self.key_word,
            "field":5
        }
        url = self.list_url + '?' + parse.urlencode(data)
        yield Request(url=url,
                      headers=self.header,
                      meta={'cookiejar': 1},
                      callback=self.parse)
複製程式碼
  1. 得到第一頁列表頁,篩選條件,得到請求的FormData
    我們在網頁進行篩選操作,可以看到類似結果:

    FormData中,我們可以複製下來後,修改的幾個變數來進行篩選:

    • pageindex:第幾頁列表頁(1 ~ )
    • fieldtype: 主題/篇名/全文/作者/關鍵詞/單位/摘要/來源
    • sorttype: 相關度/下載次數/被引頻次/最新文獻/歷史文獻
    • articletype:文獻型別
    • starttime_sc: 開始年份
    • endtime_sc: 結束年份
        def parse(self, response):
            self.header['Referer'] = response.request.url
            yield FormRequest(url=self.list_url,
                              headers = self.header,
                              method = 'POST',
                              meta = {'cookiejar': 1},
                              formdata = self.myFormData,
                              callback = self.parse_list,
                              dont_filter = True)
    複製程式碼
  2. 解析得到總列表頁數,並構造請求

    #總頁數
    paper_size = int(response.xpath('//*[@id="totalcount"]/text()').extract_first())
    #構造請求
    for page in range(1, paper_num):
        self.myFormData["pageindex"] = str(page),
        yield FormRequest(url=self.list_url,
                           headers = self.header,
                          method = 'POST',
                          meta = {'cookiejar': page+1, 'page': page},#更新會話
                          formdata = self.myFormData,
                          callback = self.parse_list_link,
                          dont_filter = True)
    複製程式碼

    注意:我們觀察請求過程,在網頁中我們是通過點選更新的,我們觀察LoadNextPage函式,可以看到請求更新資料也是通過提交表單的方式,因此我們可以構造POST請求資料。

  3. 請求詳情頁

    items = response.xpath('//a[@class="c-company-top-link"]/@href').extract()
        #可以將已爬取詳情頁數寫入檔案進行記錄
        with open('../record_page.txt', 'a') as f:
            f.write(str(response.meta['page']) + '\n')
        for item in items:
            yield Request(url = item,
                          meta={'cookiejar': response.meta['cookiejar']},#對應會話標誌
                          headers = self.header,
                          callback = self.parse_item)
    複製程式碼
  4. 解析詳情頁(示例)

    baseinfo = response.xpath('/html/body/div[@class="c-card__paper2"]')
    keywords = baseinfo.xpath('//div[contains(text(),"關鍵詞")]/following-sibling::*/a/text()').extract()
    複製程式碼

補充

為了提高爬取速度,防止ip被識別的可能,推薦阿布雲進行IP代理,申請賬號及HTTP動態隧道後,更改settings:

    DOWNLOAD_DELAY = 0.2
    DOWNLOADER_MIDDLEWARES = {
        'myspider.middlewares.RandomUserAgentMiddleware': 401,
        'myspider.middlewares.ABProxyMiddleware': 1,
    }
    AB_PROXY_SERVER = {
        'proxyServer': "http://http-dyn.abuyun.com:9020",
        'proxyUser': "xxxxxxxxxxxxxxx",#你的
        'proxyPass': "xxxxxxxxxxxxxxx"#你的
    }
複製程式碼

新增中介軟體:

proxyAuth = "Basic " + base64.urlsafe_b64encode(bytes((proxyUser + ":" + proxyPass), "ascii")).decode("utf8")
class ABProxyMiddleware(object):
    """ 阿布雲ip代理配置 """
    def process_request(self, request, spider):
        request.meta["proxy"] = proxyServer
        request.headers["Proxy-Authorization"] = proxyAuth
複製程式碼

爬蟲菜鳥,有問題請幫忙指出!