• 1.概述
  • 2.准备工作
  • 2.1 操作系统
  • 2.2 开发工具
  • 3.逻辑分析
  • 3.1 页面分析
  • 3.2 源码分析
  • 3.3 Fiddler 调试
  • 4.编写代码
  • 5.服务器托管
  • 6.生成结果
  • 7.后记


知网硕博类论文url爬虫

1.概述

  手写一个对知网的所有的硕博类论文的 URL 分地区和学科进行爬取的爬虫,将爬虫托管在服务器上运行,并将得到的初步结果保存在 txt 文件上,处理错误日志.
没有用Scrapy框架

2.准备工作

2.1 操作系统

  ubuntu16.04 64 位 , windows 64 位

2.2 开发工具

  Pycharm , Fiddler 调试工具, Chrome , putty (连接服务器), winscp(传输文件)

3.逻辑分析

3.1 页面分析

python根据作者爬取知网文献关键词 python爬取知网论文_python根据作者爬取知网文献关键词


python根据作者爬取知网文献关键词 python爬取知网论文_爬虫_02


python根据作者爬取知网文献关键词 python爬取知网论文_Python_03


代码是根据第二个入口里面的逻辑写的 肯定是要重写了

地区和学科的映射关系如下:

真的找不出是什么规律

'福建': '华东(南)',
    '上海': '华东(南)',
    '江西': '华东(南)',
    '浙江': '华东(南)',

    '新疆': '西北',
    '宁夏': '西北',
    '陕西': '西北',
    '青海': '西北',
    '甘肃': '西北',

    '湖北': '华中',
    '河南': '华中',
    '湖南': '华中',

    '海南': '华南',
    '广西': '华南',
    '广东': '华南',

    '江苏': '华东(北)',
    '安徽': '华东(北)',
    '山东': '华东(北)',

    '瑞典': '其他国家',
    '日本': '其他国家',
    '澳大利亚': '其他国家',
    '英国': '其他国家',
    '德国': '其他国家',
    '美国': '其他国家',

    '天津': '华北',
    '内蒙古': '华北',
    '山西': '华北',
    '河北': '华北',
    '北京': '华北',

    '黑龙江': '东北',
    '辽宁': '东北',
    '吉林': '东北',

    '香港': '港澳台',

    '西藏': '西南',
    '四川': '西南',
    '重庆': '西南',
    '云南': '西南',
    '贵州': '西南',

3.2 源码分析

Ctrl+U 查看源码

定位元素,右键检查,打开 Chrome 调试器,可以观察到对应的代码

python根据作者爬取知网文献关键词 python爬取知网论文_知网_04

按照静态网址的做法,首先复制需要分析的字符串,观察在源码中的位置
可以发现两者呈现的结果不一样,原因在于<div class=bodymain>这个结果是动态呈现的,通过一段js代码实现

python根据作者爬取知网文献关键词 python爬取知网论文_爬虫_05

对于动态网页,有两种解决方式:
1.selenium + chromedriver 模拟浏览器行为
2.分析网页请求,得到是哪个接口获取这方面的数据,然后再用 post 的方法发送得到 response
采用第二种.

3.3 Fiddler 调试

python根据作者爬取知网文献关键词 python爬取知网论文_python根据作者爬取知网文献关键词_06


可以得到相应的header 和 webforms

分析得到 form 中有关联的是 SearchStateJson , index

同理,点击大学可以分析到关联的 SearchStateJson 中的 value 字段

相应能够构造出地区,大学的代码,详见代码

大学页面用相似的办法处理,也是动态加载,得到学科代码subCode,页码pIDx

python根据作者爬取知网文献关键词 python爬取知网论文_知网_07

4.编写代码

1.构造查询大学请求,遍历地区,得到大学的列表
2.利用大学列表构造相应大学的 URL (make_university_url)
3.在新的 URL 内查询学科,得到相应论文的数据段
4.解析页面(parse_page)

具体实现:

#!/usr/bin/python
# vim: set fileencoding=utf-8 :

import threading
import random
import re
import io
from bs4 import BeautifulSoup
from lxml import etree
import time
import requests

get_province = {
        '0004': '福建',
        '0025': '上海',
        '0017': '江西',
        '0031': '浙江',

        '0029': '新疆',
        '0020': '宁夏',
        '0024': '陕西',
        '0021': '青海',
        '0005': '甘肃',

        '0013': '湖北',
        '0011': '河南',
        '0014': '湖南',

        '0009': '海南',
        '0007': '广西',
        '0006': '广东',

        '0016': '江苏',
        '0001': '安徽',
        '0022': '山东',

        '0036': '瑞典',
        '0035': '日本',
        '0037': '澳大利亚',
        '0033': '英国',
        '0038': '德国',
        '0034': '美国',

        '0027': '天津',
        '0019': '内蒙古',
        '0023': '山西',
        '0010': '河北',
        '0002': '北京',

        '0012': '黑龙江',
        '0018': '辽宁',
        '0015': '吉林',

        '0032': '香港',

        '0039': '西藏',
        '0026': '四川',
        '0003': '重庆',
        '0030': '云南',
        '0008': '贵州'
}

get_zone = {
    '福建': '华东(南)',
    '上海': '华东(南)',
    '江西': '华东(南)',
    '浙江': '华东(南)',

    '新疆': '西北',
    '宁夏': '西北',
    '陕西': '西北',
    '青海': '西北',
    '甘肃': '西北',

    '湖北': '华中',
    '河南': '华中',
    '湖南': '华中',

    '海南': '华南',
    '广西': '华南',
    '广东': '华南',

    '江苏': '华东(北)',
    '安徽': '华东(北)',
    '山东': '华东(北)',

    '瑞典': '其他国家',
    '日本': '其他国家',
    '澳大利亚': '其他国家',
    '英国': '其他国家',
    '德国': '其他国家',
    '美国': '其他国家',

    '天津': '华北',
    '内蒙古': '华北',
    '山西': '华北',
    '河北': '华北',
    '北京': '华北',

    '黑龙江': '东北',
    '辽宁': '东北',
    '吉林': '东北',

    '香港': '港澳台',

    '西藏': '西南',
    '四川': '西南',
    '重庆': '西南',
    '云南': '西南',
    '贵州': '西南',
}


def get_university_response(value, page_index):
    url = "http://navi.cnki.net/knavi/Common/Search/PPaper"
    headers = {
        'Accept': "text/plain, */*; q=0.01",
        'Accept-Encoding': "gzip, deflate",
        'Accept-Language': "zh-CN,zh;q=0.9",
        'Content-Length': "976",
        'Content-Type': "application/x-www-form-urlencoded",
        'Cookie': "cnkiUserKey=a96dda55-d4ee-2b66-7221-c2be612e4129; UM_distinctid=163626642d47c-0d7118a6fbe2c8-3961430f-1fa400-163626642d5ae6; Ecp_ClientId=3180515165106096065; ASP.NET_SessionId=fn4hjrmxg3nv4ng2o3tqbt41; SID_navi=1201620; Ecp_session=1; LID=WEEvREcwSlJHSldRa1FhdXNXa0hIb2hxbUg0bGRtZ01sK0lLNitEZ0d1az0=$9A4hF_YAuvQ5obgVAqNKPCYcEjKensW4IQMovwHtwkF4VYPoHbKxJw!!",
        'Host': "navi.cnki.net",
        'Origin': "http://navi.cnki.net",
        'Proxy-Connection': "keep-alive",
        'Referer': "http://navi.cnki.net/knavi",
        'User-Agent': "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/537.36",
        'X-Requested-With': "XMLHttpRequest",
        'Cache-Control': "no-cache",
    }
    form_data = {
        'SearchStateJson': '{"StateID":"","Platfrom":"","QueryTime":"","Account":"knavi","ClientToken":"","Language":"","CNode":{"PCode":"CDMD","SMode":"","OperateT":""},"QNode":{"SelectT":"","Select_Fields":"","S_DBCodes":"","QGroup":[{"Key":"Navi","Logic":1,"Items":[],"ChildItems":[{"Key":"PPaper","Logic":1,"Items":[{"Key":1,"Title":"","Logic":1,"Name":"AREANAME","Operate":"","Value":\"' + str(
            value).rjust(4,
                         '0') + '?","ExtendType":0,"ExtendValue":"","Value2":""}],"ChildItems":[]}]}],"OrderBy":"RT|","GroupBy":"","Additon":""}}',
        'displaymode': 1,
        'index': 1,
        'pagecount': 21,
        'pageindex': page_index,
        # 'random': 0.350068384544433,
        'random': random.random(),

    }
    response = requests.request("POST", url, data=form_data, headers=headers, timeout=30)
    return response.text


def get_university_id(html_text):
    code_list = re.findall(r"baseid=\w+", html_text)
    title_list = re.findall(r"title=\"[\u4e00-\u9fa5]*·?[\u4e00-\u9fa5]*\(?[\u4e00-\u9fa5A-Za-z\s]*\)?[^\x00-\xff]?\">",
                            html_text)
    if (len(title_list) > 0):
        del title_list[0]
    # print(code_list)
    # print(title_list)
    code_list1 = []
    title_list1 = []
    for i in code_list:
        code_list1.append(str(i)[7:])
    for i in title_list:
        title_list1.append(str(i)[7:-2])
    return code_list1, title_list1


def write_in_file(code_list1, title_list1):
    fpath = '/root/university_id_name.out'
    with io.open(fpath, 'a', encoding='utf-8') as f:
        for i in range(len(code_list1)):
            f.write(code_list1[i] + '_' + title_list1[i] + '\n')


def make_university_url(code_list):
    root_url = 'http://navi.cnki.net/knavi/PPaperDetail?pcode=CDMD&logo='
    for i in code_list:
        url = root_url + i[0:5]
        value = i[-4:]
        # print(url)
        for subCode in range(1, 13):  # (1,13) 学科编号
            for pIDx in range(0, 1):  # (需要额外判断) 页面下标
                get_article_by_subject_response(url, subCode, pIDx, value)


# page_index 下标从 0 开始
def get_article_by_subject_response(url, subCode, pIDx, value):
    post_url = 'http://navi.cnki.net/knavi/PPaperDetail/GetArticleBySubject'
    headers = {
        'Accept': "*/*",
        'Accept-Encoding': "gzip, deflate",
        'Accept-Language': "zh-CN,zh;q=0.9",
        'Content-Length': "75",
        'Content-Type': "application/x-www-form-urlencoded; charset=UTF-8",
        # 'Cookie': "cnkiUserKey=a96dda55-d4ee-2b66-7221-c2be612e4129; UM_distinctid=163626642d47c-0d7118a6fbe2c8-3961430f-1fa400-163626642d5ae6; Ecp_ClientId=3180515165106096065; ASP.NET_SessionId=fn4hjrmxg3nv4ng2o3tqbt41; SID_navi=1201620; Ecp_session=1; LID=WEEvREcwSlJHSldRa1FhdXNXa0hIb2hyQlVyRFlJczA2Mm1YYzF4RzNGUT0=$9A4hF_YAuvQ5obgVAqNKPCYcEjKensW4IQMovwHtwkF4VYPoHbKxJw!!; Ecp_IpLoginFail=18052136.17.61.215",
        'Host': "navi.cnki.net",
        'Origin': "http://navi.cnki.net",
        'Proxy-Connection': "keep-alive",
        'Referer': "http://navi.cnki.net/knavi/PPaperDetail?pcode=CDMD&logo=GHAGU",
        'User-Agent': "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Mobile Safari/537.36",
        'X-Requested-With': "XMLHttpRequest",
        'Cache-Control': "no-cache",
    }
    form_data = {
        'pcode': 'CDMD',
        'baseID': url[-5:],
        'subCode': str(subCode).rjust(2, '0'),
        'orderBy': 'RT|DESC',
        'scope': '%u5168%u90E8',
        'pIdx': pIDx
    }
    try:
        response = requests.request('POST', post_url, data=form_data, headers=headers, timeout=30)
    except:
        print('Error:'+pIDx)
    parse_page(response.text, value, subCode)
    # 获取页码数
    soup = BeautifulSoup(response.text, 'html.parser')
    pIDx_max = soup.find('span', attrs={'id': 'partiallistcount'}).string
    pIDx_max = int(pIDx_max)
    for pIDx in range(1, int(pIDx_max / 20 + 2)):
        form_data = {
            'pcode': 'CDMD',
            'baseID': url[-5:],
            'subCode': str(subCode).rjust(2, '0'),
            'orderBy': 'RT|DESC',
            'scope': '%u5168%u90E8',
            'pIdx': pIDx
        }
        try:
            response = requests.request('POST', post_url, data=form_data, headers=headers, timeout=30)
            parse_page(response.text, value, subCode)
        except:
            with io.open('/root/ErrorLog.out','a',encoding='utf-8') as f:
                f.write(url+'\n')



def parse_page(response_text, value, subCode):
    with io.open('/root/response_text.out', 'w', encoding='utf-8') as f:
        f.write(response_text)
    soup = BeautifulSoup(response_text, 'html.parser')
    tag_list = soup.find_all('td', attrs={'class': 'name'})
    if (len(tag_list) == 0):
        time.sleep(1)
        return
    if (len(tag_list) > 0):
        del tag_list[0]
    # print(tag_list) type is bs4.element.Tag
    with io.open('/root/result.out', 'a', encoding='utf-8') as f:
        if (len(tag_list) > 0):
            for tag in tag_list:
                string = tag.a['href']
                indexx = string.find('sfield=FN&')
                province = get_province[value]
                zone = get_zone[province]
                f.write('http://kns.cnki.net/kcms/detail/detail.aspx?' + string[
                                                                         indexx:] + ' ' + province + ' ' + zone + ' ' + str(
                    subCode) + '\n')


def main():
    code_list = []
    title_list = []
    for value in range(1, 40):  # (1,40) 大学代码遍历
        for page_index in range(1, 4):  # 检索大学页面下标
            html_text = get_university_response(value, page_index)
            code_list1, title_list1 = get_university_id(html_text)
            code_list1 = [i + '_' + str(value).rjust(4, '0') for i in code_list1]  # new
            code_list.extend(code_list1)
            title_list.extend(title_list1)
    # 获取所有大学的代码和名字 大学代码均为5位
    # write_in_file(code_list, title_list)  # 写入文件 university_id_name.txt
    # print(code_list) # GJUYU_0025
    thread = []
    for i in range(10):
        t = threading.Thread(target=make_university_url, args=(code_list))
        thread.append(t)
    make_university_url(code_list)  # 构造大学url

main()

5.服务器托管

利用 winscp 将代码传输到相应服务器,Tmux 分屏实现托管

Tmux 命令可参考博客
终端利器 - tmux配置与使用

tmux
python3 cnki_2.py
按Ctrl+B D #返回主界面

6.生成结果

python根据作者爬取知网文献关键词 python爬取知网论文_爬虫_08

19 小时跑完所有结果(450MB),考虑到单线程还是比较快的
ErrorLog.out保存错误日志
response_rext.out保存最后一次的response
result.out为最终结果

python根据作者爬取知网文献关键词 python爬取知网论文_Python_09

7.后记

整个过程坑点满满,随便写几个:
1.大学的名字格式千奇百怪,还有国外的,写正则换了好几次
2.有某个大学的括号是全角半角的混用的…知网前端程序员背锅
3.cookie是不必要的,甚至会因为点击频率太高,导致得不到对应的respsonse
4.搬运到服务器上遇到了一点编码上的小问题

启发:
熟练了 post 方法处理动态页面
Fiddler 实在太强大了,另外 postman 不太会用

后续:
开发速度很快,但被明示结果的呈现太简单暴力…应该是要分文件夹,细分文件夹,以及得到更多的字段,然后开始要解析具体的论文页面