[原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

这里只有作者精心编写的学习经历!
回复
头像
hellohappy
网站管理员
网站管理员
帖子: 282
注册时间: 2018年11月18日, 14:27
Been thanked: 2 time

#1 [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 hellohappy » 2019年3月11日, 19:48



目录:
    前言:
    正文:
        爬虫的基本步骤:
        编写基本python代码:
        错误处理:
    python代码:
    统计年鉴下载地址:


前言:
    做研究的时候,要用到各种城市年度统计数据,我第一个就想到了《中国城市统计年鉴》。因为他也是论文里面最常提到的。其次就是各区域的统计年鉴。
    找了很久,发现网上没有完整可用的,或者说是可信赖的统计数据,因为大多数都是自己整理的某几年的Excel甚至pdf版。这些数据不利于我批量整理,用来补全数据倒是可以。所以,我找到了中国知网的统计年鉴数据库。网站是http://data.cnki.net/Yearbook
    这个网站里面的数据有Excel版本的,方便批量整理,同时有统一的格式,不像网上的数据都是东一块,西一块,拼拼凑凑,而且很多变量都不全,变量名字也不统一。
    找到网站,下一步就是下载这些Excel。但是这么多年份,这么多表格,我不太可能自己一个个下载,所以就用到了爬虫!这个爬虫我居然在网上找到了一个可用的源码!当然,后来估计是知网稍微加强了反爬,所以我也稍微修改了一下源码,以保证能完整下载下整个统计年鉴。

正文:

    中国知网这个年鉴库是需要学校购买的,由于我们学校没有买,所以我向同学借了一个上网帐号跑到中大的图书馆去爬。
    在有初步准备的基础上,在中大图书馆改代码加上爬统计年鉴,还是花了一整天(接近14个小时)。原因是知网总是随机性连接中断,以及过度访问会直接拒绝一段时间的访问。(这里没有用到那些高深的爬虫技术,比如ip代理池什么的,因为真的没必要,也就爬那一点点东西,同时,人家是固定ip段才能访问,ip代理肯定用不上)

    爬虫的基本步骤:

        找到中国知网的年鉴结构:
            通过浏览知网统计年鉴网页的各种不同的年鉴,可以发现一些规律;
                1.同一年份的同一本统计年鉴的网址是固定的,基本都是 http: //data.cnki.net/yearbook/Single/NXXXXXXXXXX ;其中X表示一个数字。
                2.同一年份的同一本统计年鉴的网页的翻页是通过 XMLHttpRequest 来传递信息的。
                3.每一个网页结构都十分相似。Excel的下载地址也是基本不会变,为 http: //data.cnki.net/download/excel?filecode=NXXXXXXXXXXXXXXXX ,其中X表示一个数字。
知网爬虫基本结构.png

    编写基本python代码:

        1.通过 XMLHttpRequest 获取这个统计年鉴下的每一个网页的信息
        2.通过python的 BeautifulSoup 库,解析网页中的所有Excel下载地址
        3.通过Excel地址下载Excel,并保存到电脑中
        以上步骤,都有一个前辈做过了,我直接贴出他的源码,源码也有源站博客的地址。
icx_coffee写的源码
Show

Code: 全选

#本文来自 icx_coffee 的CSDN 博客
#全文地址请点击:https://blog.csdn.net/icx_coffee/article/details/53234334?utm_source=copy
# -*- coding: utf-8 -*-
import urllib.request
from bs4 import BeautifulSoup
import requests
import time
import random
import re


def get_result(ybcode,page=1): #数据的请求
data = {'ybcode': ybcode, 'entrycode': '', 'page': page, 'pagerow': '20'}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
url = "http://data.cnki.net/Yearbook/PartialGetCatalogResult"
params = urllib.parse.urlencode(data).encode(encoding='utf-8')
req = urllib.request.Request(url, params, headers)
r = urllib.request.urlopen(req)
res = str(r.read(),'utf-8')
return res

def get_pageno(ybcode): #获取总页数
soup = BeautifulSoup(get_result(ybcode), 'lxml')
pages=int(soup.select('.s_p_listl')[0].get_text().split("共")[2].split('页')[0])
print ('总共'+pages+'页')
return pages


def dataclear(data): #数据的清理,除去文本中所有的\n和\r
data=re.sub('\n+',' ',data)
data = re.sub('\r+', ' ', data)
data=re.sub(' +',' ',data)
return data


def filedata(ybcode): #下载知网的统计年鉴之类的所有excel表
pageno=get_pageno(ybcode)
for i in range(1,pageno+1,1):
print ('########################################当前第'+str(i)+'页###################################')
soup=BeautifulSoup(get_result(ybcode,i),'lxml')
for j in soup.select('tr'):
s=BeautifulSoup(str(j),'lxml')
if len(s.select('img[src="/resources/design/images/nS_down2.png"]'))==0:
pass
else:
try:
if len(BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(3) > a'))>=2:
title= str(BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(1) > a')[0].get_text())
url= 'http://data.cnki.net'+BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(3) > a')[1].get('href')
title=dataclear(title) #若不清洗数据,则文件名中会包含\n等特殊字符,导致文件下载错误
filedown(title,url)
print(title)
except Exception as e:
print ('error:-------------------'+str(e))
pass

def filedown(title,url): #文件下载函数
try:
r = requests.get(url)
with open(title + ".xls", "wb") as code:
code.write(r.content)
except Exception as e:
pass
x = random.randint(1,2)
time.sleep(x)

if __name__=='__main__':
ybcode = 'N2013060059' #更改此项可下载其他年鉴
filedata(ybcode)
由于有严格的缩进要求,提供python源码下载地址:
知网爬虫.zip
(1.29 KiB)
知网爬虫.zip
(1.29 KiB) 尚未被下载

    错误处理:
        你以为上面这些就结束了?没有,如果网址访问不稳定或者做了反爬,你还要进行一定的反反爬处理和错误处理。
        错误处理很简单,判断通过链接返回的内容是否符合预期,比如你下载Excel文件,结果返回了一个无权访问的网页,显然是错误的。(理论上就是应该受到一个正常的含有数字,变量等等信息的Excel表。)
        错误的原因一般是知网检测到了你的恶意访问,发现有同一个ip,同一个请求头,进行频繁的访问和下载资源。这时候你的解决方法有:假装你是正常访问,更换ip,更换请求头,甚至有可能你直接机器绕过验证码。但是由于本身请求的都是下载的资源,而且ip是固定的!只能是中大的ip,那就只好,等。。。被检测出我访问频繁,我就不那么频繁访问,被禁止访问,我就等几分钟再访问。这是最最低端,但是有效的方法,哈哈哈。所以其实我这只爬虫是很友善的!

python代码:

    好了,下面贴出修改过以后的python代码,代码很短很短,但是被实践检验,他是十分有效的。我觉得这个代码也是十分适合入门python爬虫的代码。

Code: 全选

# -*- coding: utf-8 -*-
import urllib.request
from bs4 import BeautifulSoup
import requests
import time
import random
import re


def get_result(ybcode,page=1): #数据的请求
data = {'ybcode': ybcode, 'entrycode': '', 'page': page, 'pagerow': '20'}
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
}
url = "http://data.cnki.net/Yearbook/PartialGetCatalogResult"
params = urllib.parse.urlencode(data).encode(encoding='utf-8')
req = urllib.request.Request(url, params, headers)
r = urllib.request.urlopen(req)
res = str(r.read(),'utf-8')
return res

def get_pageno(ybcode): #获取总页数
soup = BeautifulSoup(get_result(ybcode), 'lxml')
pages=int(soup.select('.s_p_listl')[0].get_text().split("共")[2].split('页')[0])
print('总共'+str(pages)+'页')
return pages


def dataclear(data): #数据的清理,除去文本中所有的\n和\r
data=re.sub('\n+',' ',data)
data = re.sub('\r+', ' ', data)
data=re.sub(' +',' ',data)
return data


def filedata(ybcode): #下载知网的统计年鉴之类的所有excel表
pageno=get_pageno(ybcode)
for i in range(1,pageno+1,1):
print ('########################################当前第'+str(i)+'页###################################')
soup=BeautifulSoup(get_result(ybcode,i),'lxml')
for j in soup.select('tr'):
s=BeautifulSoup(str(j),'lxml')
if len(s.select('img[src="/resources/design/images/nS_down2.png"]'))==0:
pass
else:
try:
if len(BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(3) > a'))>=2:
title= str(BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(1) > a')[0].get_text())
url= 'http://data.cnki.net'+BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(3) > a')[1].get('href')
title=dataclear(title) #若不清洗数据,则文件名中会包含\n等特殊字符,导致文件下载错误
filedown(title,url)
print(title)
except Exception as e:
print ('error:-------------------'+str(e))
pass

def filedown(title,url): #文件下载函数
try:
r = requests.get(url)
with open(title + ".xls", "wb") as code:
code.write(r.content)
except Exception as e:
pass
x = random.randint(3,4)
time.sleep(x)

if __name__=='__main__':
ybcode = 'N2016030128' #更改此项可下载其他年鉴
filedata(ybcode)

#<a href="/Yearbook/Single/N2018050234">2017年</a>
#<a href="/Yearbook/Single/N2017060038">2016年</a>
#<a href="/Yearbook/Single/N2016030128">2015年</a>
#<a href="/Yearbook/Single/N2015040001">2014年</a>
#<a href="/Yearbook/Single/N2014050073">2013年</a>
#<a href="/Yearbook/Single/N2013040146">2012年</a>
#<a href="/Yearbook/Single/N2012020070">2011年</a>
#<a href="/Yearbook/Single/N2011040042">2010年</a>
#<a href="/Yearbook/Single/N2010042092">2009年</a>
#<a href="/Yearbook/Single/N2009060160">2008年</a>
#<a href="/Yearbook/Single/N2008060073" class="current">2007年</a>
#<a href="/Yearbook/Single/N2008060072">2006年</a>
#<a href="/Yearbook/Single/N2006090503">2005年</a>
#<a href="/Yearbook/Single/N2005110391">2004年</a>
#<a href="/Yearbook/Single/N2005110393">2003年</a>
#<a href="/Yearbook/Single/N2005110392">2002年</a>
#<a href="/Yearbook/Single/N2006010420">2001年</a>
#<a href="/Yearbook/Single/N2006010421">2000年</a>
#<a href="/Yearbook/Single/N2005110394">1999年</a>
#<a href="/Yearbook/Single/N2005110395">1998年</a>
#<a href="/Yearbook/Single/N2005110396">1997年</a>
#<a href="/Yearbook/Single/N2005110397">1996年</a>
#<a href="/Yearbook/Single/N2005110398">1995年</a>
#<a href="/Yearbook/Single/N2005110399">1993-1994年</a>
#<a href="/Yearbook/Single/N2005110400">1992年</a>
#<a href="/Yearbook/Single/N2005110401">1991年</a>
#<a href="/Yearbook/Single/N2005120801">1990年</a>
#<a href="/Yearbook/Single/N2006020063">1989年</a>
#<a href="/Yearbook/Single/N2006010585">1988年</a>
#<a href="/Yearbook/Single/N2006020064">1987年</a>
#<a href="/Yearbook/Single/N2006010586">1986年</a>
#<a href="/Yearbook/Single/N2006010587">1985年</a>
由于有严格的缩进要求,提供python源码下载地址:

统计年鉴下载地址:

本人爬取的1985到2017年中国城市统计年鉴Excel版下载地址(内容版权应该归知网所有?但是知识是人人可以共享的。)
https://www.postgraduate.top/viewtopic.php?f=2&t=84

hellohappy

谢谢老板~

使用微信扫描二维码完成支付


Link:
Hide post links
Show post links

gzl1996
帖子: 4
注册时间: 2019年9月26日, 16:43
Has thanked: 1 time

#2 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 gzl1996 » 2019年9月26日, 17:04

感谢您的分享,我想请问我为何使用pycharm+py3.7环境无法运行,提示有缩进问题?

Link:
Hide post links
Show post links

头像
hellohappy
网站管理员
网站管理员
帖子: 282
注册时间: 2018年11月18日, 14:27
Been thanked: 2 time

#3 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 hellohappy » 2019年9月26日, 21:10

你好,不要直接复制粘贴网页上的python代码,直接下载我给出的zip源码文件,因为python对缩进十分严格,复制粘贴的可能不对。
其次,我当时的python版本是3.6(虽然3.7一般不会不兼容)

Link:
Hide post links
Show post links

gzl1996
帖子: 4
注册时间: 2019年9月26日, 16:43
Has thanked: 1 time

#4 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 gzl1996 » 2019年9月26日, 21:35

感谢您的回复,我现在可以运行了,是因为我自己少安装了一个依赖包。现在下载的文件文件名和扩展名都正常,但缺少内容,都是一个11KB的知网登陆界面,我确定我学校是购买了年鉴库的,在网页上也可以下载。您知道如何解决这种情况吗?

Link:
Hide post links
Show post links

头像
hellohappy
网站管理员
网站管理员
帖子: 282
注册时间: 2018年11月18日, 14:27
Been thanked: 2 time

#5 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 hellohappy » 2019年9月26日, 21:39

这个程序是一年半前写的,可能知网更新了他的安全机制和反爬虫机制。我现在没精力去弄这个代码(而且我们学校没有买,我得跑去中大蹭网才能调试),希望谁有空可以更新一下程序,可以的话再分享出来给大家用。

Link:
Hide post links
Show post links

gzl1996
帖子: 4
注册时间: 2019年9月26日, 16:43
Has thanked: 1 time

#6 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 gzl1996 » 2019年9月26日, 21:41

hellohappy 写了:
2019年9月26日, 21:39
这个程序是一年半前写的,可能知网更新了他的安全机制和反爬虫机制。我现在没精力去弄这个代码(而且我们学校没有买,我得跑去中大蹭网才能调试),希望谁有空可以更新一下程序,可以的话再分享出来给大家用。

感谢您的解答!谢谢~

Link:
Hide post links
Show post links

gzl1996
帖子: 4
注册时间: 2019年9月26日, 16:43
Has thanked: 1 time

#7 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 gzl1996 » 2019年9月26日, 22:15

应该是cookies受限了,添加一下cookies和agent到headers里即可~

Link:
Hide post links
Show post links

123456
帖子: 1
注册时间: 2019年10月10日, 21:45

#8 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 123456 » 2019年10月10日, 21:49

if len(BeautifulSoup(str(j), 'lxml').select('td:nth-of-type(3) > a'))>=2:

这一段中的'td:nth-of-type(3) > a'是什么啊?


 

Link:
Hide post links
Show post links

头像
hellohappy
网站管理员
网站管理员
帖子: 282
注册时间: 2018年11月18日, 14:27
Been thanked: 2 time

#9 Re: [原创][python]知网统计年鉴爬虫--爬取中国城市统计年鉴为例

未读文章 hellohappy » 2019年10月10日, 22:43

这是BeautifulSoup中select方法的语法,自己去搜索一下就知道了;
需要预先知道的一些知识点是 1.html的dom 和 2.css选择器
如果知道这两个,上面的语句应该是能大概理解的。
    其中nth-of-type(n)选择器匹配同类型中的第n个同级兄弟元素
    td:nth-of-type(3)指每个td元素匹配同类型中的第3个同级兄弟元素

Link:
Hide post links
Show post links


回复