Scrapy实战:爬取期刊遇到的坑与技术总结

参考博客及资料

最近要写一个数据分析的项目,需要根据关键词爬取近十年期刊的主要信息,记录一下爬取过程中遇到的问题

分析

我们要爬取学术论文详情页的主题,摘要等信息,主要步骤和其他网站的爬取大致相似:一是要根据关键词搜索到列表页;二是要从列表页请求得到详情页,从详情页取得我们所要的信息。

  • 入口页面:[http://kns.cnki.net/kns/brief/default_result.aspx]
  • 搜索后,js动态渲染的请求列表页面:[http://kns.cnki.net/kns/brief/brief.aspx?...]
    这里我们打开Developer Tools观察请求头和参数



    这里的关键信息: ① 请求参数,我们观察到请求的关键词在字段KeyValue中(GET请求); ② cookiereferer:如果没有在请求头部加入referer,我们将无法打开这个列表页,如果没有在头部中加入cookie,我们请求后得到的页面内容是不完整的!注意:iframe列表详情页只有从入口页面请求渲染后才能得到,这步请求不能省略!

  • 从列表页的链接中解析得到详情页[http://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)
    
  3. 如何请求列表页

     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',
             '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)
    
  4. 如何解析列表页

    • 获得列表总页数:
      response.xpath('//span[@class="countPageMark"]/text()').extract_first()
      max_page = int(page_link.split("/")[1])
      
    • 请求每个列表页
                    data = {
                    "curpage": page_num,#循环更改
                    "RecordsPerPage": 20,
                    "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()
      
  5. 如何解析详情页(只是一个示例,有很多种解析方法)
     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()
    

即使解决了cookie和referer问题,想要批量爬取还是有一定难度,主要是一个超时问题无法访问,还可能出现验证码。我尝试使用CrawlProcess分批爬取还是不能解决,可能需要改进代码。

欢迎fork我的Github项目

2018.12.11 更新

使用电脑端爬取经常会遇到各种问题,比如超时访问获取不到网页;使用手机知网的接口限制少点,爬取会简单一点http://wap.cnki.net/touch/web,爬取的步骤是类似的。

  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)
    
  2. 得到第一页列表页,筛选条件,得到请求的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)
      
  3. 解析得到总列表页数,并构造请求

     #总页数
     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请求数据。

  4. 请求详情页

     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)
    
  5. 解析详情页(示例)
     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

爬虫菜鸟,有问题请帮忙指出!


 上一篇
Truffle+Vue+Web3 1.0创建一个以太坊Dapp Truffle+Vue+Web3 1.0创建一个以太坊Dapp
参考资料 使用 Web3 和 Vue.js 来创建你的第一个以太坊 dAPP web3 1.0 API 开发环境 Windows10 web3 1.0 编写第一个Solidity智能合约一个简单的例子是编写一个可以注册,保存社区成员信息
2018-12-10
下一篇 
知识图谱-从构建到呈现 知识图谱-从构建到呈现
参考资料 Apache Jena 基于 REfO 的 KBQA 实现及示例 知识图谱-给AI装个大脑 技术路线 构建实体概念 工具:Protégé 构建三元组数据库TDB 数据采集:爬虫基础(百度百科,去哪儿网,etc) Apach
2018-11-05
  目录