]+href\s*=\s*#\s*onclick\s*=\s*"location.href='(.*?)'">,正好由此简单谈一谈我对正则表达式的看法:
话虽如此,但还是有必要掌握基本的构造模式滴。
笔者选择了3个不同主题文献的 DOI
组成了一个列表,从运行结果可见,飞行器那篇文献匹配为空,笔者特意手动打开链接,发现不能正常访问,这说明我们得到的 pdf 资源列表中可能存在不可用的项 ,需要筛选那些为空的项 (在后续方法中会考虑这点);另外,能访问的链接也未必能正常下载,需要额外考虑这些情况 。
输出中有警告信息,暂且不要管
2020-12-08 晴
Beautiful Soup
Beautiful Soup,不知道设计者为何取了个这个名字,但使用起来确实感觉 very beautiful 就是了。语法简单,而且可以对 html 网页的语法问题进行修复,唯一的瑕疵就是有些慢,不过能理解,毕竟是用纯 Python 编写的 (正则 和 Lxml 是 C 语言写的)。
需要安装两个模块 (别忘了先进入虚拟环境) :
conda install beautifulsoup4
conda install html5lib
直接上手,下面给出 用 BeautifulSoup 匹配超文本以获取匹配内容列表 的 Python3 实现 (scraping_using_bs4.py):
from bs4 import BeautifulSoup
import re
def get_link_using_bs4 ( html, parser= 'html5lib' ) :
try :
soup = BeautifulSoup( html, parser)
except :
print ( 'parser not available, now use the default parser "html.parser"...' )
parser = 'html.parser'
soup = BeautifulSoup( html, parser)
soup. prettify( )
div = soup. find( 'div' , attrs= {
'id' : 'buttons' } )
if div:
a = div. find( 'a' , attrs= {
'href' : '#' } )
if a:
a = a. attrs[ 'onclick' ]
return re. findall( r"location.href\s*=\s*'(.*?)'" , a) [ 0 ]
return None
if __name__ == '__main__' :
from download import download
dois = [ '10.1016/j.apergo.2020.103286' ,
'10.1016/j.jallcom.2020.156728' ,
'10.3964/j.issn.1000-0593(2020)05-1356-06' ]
links = [ ]
for doi in dois:
html = download( doi)
link = get_link_using_bs4( html)
if link:
links. append( link)
for link in links:
print ( link)
运行结果 (省去警告):
Downloading: https://sci-hub.do/10.1016/j.apergo.2020.103286
Downloading: https://sci-hub.do/10.1016/j.jallcom.2020.156728
Downloading: https://sci-hub.do/10.3964/j.issn.1000-0593(2020)05-1356-06
//sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true
//sci-hub.do/downloads/2020-11-23/ac/tsvinkinberg2021.pdf?download=true
对代码的说明:
上面的代码采用了 BeautifulSoup + 正则表达式,主要做了如下几件事:
soup.prettify()
→ \rightarrow → 修复 html 文本存在的问题,规范格式
soup.find()
→ \rightarrow → 根据所给标签属性定位元素位置,下面是定义 (element.py):
def find ( self, name= None , attrs= {
} , recursive= True , text= None ,
** kwargs) :
"""Return only the first child of this Tag matching the given
criteria."""
r = None
l = self. find_all( name, attrs, recursive, text, 1 , ** kwargs)
if l:
r = l[ 0 ]
return r
re.findall(pattern, string)
→ \rightarrow → 其中 string
是前面获得的 onclick
属性的内容,内容很规整,此时采用正则表达式处理更方便 (来自未来:其实是笔者不太熟悉 BeautifulSoup…)
虽然没有直接导入 html5lib
,但不代表不需要 (除非你只需要用 html.parser
)
通过条件判断是否为 None
,初步筛选了用不了的链接
Lxml ⋆ \star ⋆
和 BeautifulSoup 一样,使用 Lxml 模块的第一步也是将有可能不合法的 html 解析为统一格式;同样地,Lxml 也可以正确解析属性两侧缺失的引号,并闭合标签,不过该模块没有额外添加
和
标签,这些都不是标准 XML 的要求,因此对于 Lxml 来说,插入它们是不必要的。[1]
本小节我们将使用选择器 来定位元素,包括 CSS选择器 和 XPath选择器 。
对于它们的相关说明,读者可以通过以下参考链接查阅:
笔者这里列出几个基本但常用的选择器表达式,见下表:
表 3.1 常用选择器表达式的比较
[1]
选择器描述
XPath选择器
CSS选择器
选择所有链接
‘//a’
‘a’
选择类名为"main"的 div 元素
‘//div[@class=“main”]’
‘div.main’
选择ID为"list"的 ul 元素
‘//ul[@id=“list”]’
‘ul#list’
从所有段落中选择文本
‘//p/text()’
None
选择所有类名中包含’test’的 div 元素
‘//div[contains(@class, ‘test’)]’
None
选择所有包含链接或列表的 div 元素
‘//div[a|ul]’
‘div a, div ul’
选择 href 属性中包含 google.com 的链接
‘//a[contains(@href, “google.com”)]’
None
在后续实践中可能用到的CSS选择器的补充:
我们可以在开发者工具的控制台 (Console) 中使用这些选择器来预先调试我们的选择器字符串,看看能否正常筛选。对于CSS选择器,其在浏览器中的选择器使用格式为 $('选择器表达式')
;对于XPath选择器,其在浏览器中的选择器使用格式为 $x('选择器表达式')
在浏览器控制台中使用选择器
下面先演示CSS选择器在开发者工具中如何使用:
随意选一片文献,比如笔者选择了 https://sci-hub.do/10.1016/j.apergo.2020.103286
F12 打开开发者工具,切换到 Console 控制台
键入我们的CSS选择表达式:$('div#buttons a')
或者 $('div#buttons > ul > li > a')
,发现正确选择了我们的目标标签 :
对于XPath选择器,我们甚至可以直接找到 onclick
属性内容,只需输入$x('//div[@id="buttons"]/ul/li/a')[0].attributes[1].textContent
:
在代码中使用选择器
在浏览器中我们已经见识了选择器的方便与强大,下面看看代码中怎么使用它们。
安装 lxml 和 cssselect 模块:conda install cssselect
、conda install lxml
下面给出 用 lxml 以及一种选择器匹配超文本以获取匹配内容列表 的 Python3 实现 (scraping_using_lxml.py):
from lxml. html import fromstring
def get_link_cssselect ( html) :
try :
tree = fromstring( html)
a = tree. cssselect( 'div#buttons > ul > li > a' ) [ 0 ]
onclick = a. get( 'onclick' )
return onclick
except Exception as e:
print ( 'error occurred: ' , e)
return None
def get_link_xpath ( html) :
try :
tree = fromstring( html)
a = tree. xpath( '//div[@id="buttons"]/ul/li/a' ) [ 0 ]
onclick = a. get( 'onclick' )
return onclick
except Exception as e:
print ( 'error occurred: ' , e)
return None
def test_selector ( selector) :
from download import download
dois = [ '10.1016/j.apergo.2020.103286' ,
'10.1016/j.jallcom.2020.156728' ,
'10.3964/j.issn.1000-0593(2020)05-1356-06' ]
links = [ ]
for doi in dois:
html = download( doi)
link = selector( html)
if link:
links. append( link)
for link in links:
print ( link)
print ( 'Done' )
if __name__ == '__main__' :
print ( 'test_cssselect(): ' )
test_selector( get_link_cssselect)
print ( 'test_xpath(): ' )
test_selector( get_link_xpath)
运行结果 (省去警告):
test_cssselect():
Downloading: https://sci-hub.do/10.1016/j.apergo.2020.103286
Downloading: https://sci-hub.do/10.1016/j.jallcom.2020.156728
Downloading: https://sci-hub.do/10.3964/j.issn.1000-0593(2020)05-1356-06
error occurred: Document is empty
location.href='//sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true'
location.href='//sci-hub.do/downloads/2020-11-23/ac/tsvinkinberg2021.pdf?download=true'
Done
test_xpath():
Downloading: https://sci-hub.do/10.1016/j.apergo.2020.103286
Downloading: https://sci-hub.do/10.1016/j.jallcom.2020.156728
Downloading: https://sci-hub.do/10.3964/j.issn.1000-0593(2020)05-1356-06
error occurred: list index out of range
location.href='//sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true'
location.href='//sci-hub.do/downloads/2020-11-23/ac/tsvinkinberg2021.pdf?download=true'
Done
对代码和结果的说明:
两种选择器最终都达到了目标要求,而且两种方法的代码只有一行的差别,即 tree.cssselect()
以及 tree.xpath()
调用的那一行
lxml 模块函数 fromstring(...)
用于统一 html 格式,并返回一个 document
或 element
对象
cssselect()
和 xpath()
均返回一个匹配列表,鉴于 sci-hub 目标元素所在层级中只存在一个匹配,所以我们取列表中的 0 位置元素
get(attr)
方法用于获取特定标签属性的内容,实践中我们要找的是
标签的 onclick
属性
注意异常的处理 (第3个 doi 获取的网页是不合法的)
这里还没有筛选完 ,我们可以沿用 BeautifulSoup 小节的方式,采用正则表达式作为最后的筛选工作:return re.findall(r"location.href\s*=\s*'(.*?)'", onclick)[0]
,读者可以自行补上 (需要导入 re 模块)
2020-12-09 晴
3.2.3 根据获得的 pdf 链接执行下载
下载链接得到了,我们通过组装链接构成绝对 url,然后使用 request 模块的 get()
方法请求资源,最后将获得的内容以二进制流 的操作写入文件即可。
下面给出 给定 DOI 执行一次相应文献的 pdf 下载 的 Python3 实现 (download.py):
import requests
def download_pdf ( url, user_agent= "sheng" , proxies= None , num_retries= 2 ) :
headers = {
'User-Agent' : user_agent}
url = 'https:{}' . format ( url)
print ( 'Downloading: ' , url)
try :
resp = requests. get( url, headers= headers, proxies= proxies, verify= False )
if resp. status_code >= 400 :
print ( 'Download error: ' , resp. status_code)
if num_retries and 500 <= resp. status_code < 600 :
return download( url, user_agent, proxies, num_retries- 1 )
with open ( 'file.pdf' , 'wb' ) as fp:
fp. write( resp. content)
except requests. exceptions. RequestException as e:
print ( 'Download error' , e)
if __name__ == '__main__' :
doi = '10.1016/j.apergo.2020.103286'
html = download( doi)
from scraping_using_lxml import get_link_xpath
url = get_link_xpath( html)
download_pdf( url)
print ( 'Done.' )
运行结果:
对代码的说明:
注意 url 的拼接格式
注意是用 'wb'
,即二进制流写入的方式打开的文件
测试代码中使用的是 XPath 选择器提取下载链接,这里换成 3.2.2 节中任意一种方式都可行 (可能有一些细节需要变化)
这里的文件名暂时用一个比较固定的 file.pdf
,如果执行批量下载,我们肯定得找一个能概括此文献的文本作为其名称,以方便我们后续查阅,容易想到文献标题是一个不错的名称候选,因此我们在下载之前,还需要抓取文献的标题 (或其他能唯一标识文献的文本),3.2.4 节会介绍改进方法
3.2.4 看看哪些地方仍需改进
磁盘文件合法命名
对于文件名称,我们有以下要求:
不重复 (在目录中唯一地标识一个文件)
有意义 (便于查找)
综上,我们选择文献标题作为文件名 (不要抬杠)。那么问题来了,能不能不做任何转换就拿来用?比如其中一篇文献的题名为:
Implementing Virtual Reality technology for safety training in the precast/ prestressed concrete industry. Applied Ergonomics, 90, 103286.
我们注意到,此标题中含有 /
,而文件名称不能含有 \ / ? * < > | : "
(共9个字符),所以不能直接拿来作为文件名称。我们需要作一些转换使得以标题作为文件名合法,且限制长度在操作系统要求的最大值之内 (WIn10 是 260字节,但经笔者测试实际最大命名长度低于此值。保险起见,我们默认设置长度为 128 字节)。代码非常简单:
import re
def get_valid_filename ( filename, name_len= 128 ) :
return re. sub( r'[^0-9A-Za-z\-,._;]' , '_' , filename) [ : name_len]
if __name__ == '__main__' :
title = r'''Implementing Virtual Reality technology for safety training in the precast/ prestressed concrete industry. Applied Ergonomics, 90, 103286.'''
print ( get_valid_filename( title) )
print ( get_valid_filename( title, 40 ) )
运行结果:
Implementing Virtual Reality technology for safety training in the precast_ prestressed concrete industry. Applied Ergonomics, 90, 103286.
Implementing Virtual Reality technology
从结果可见,/
被替换成了下划线 _
,其余合法字符没变;另外,第二行输出被限制了长度。
2020-12-10 晴
获得文件所需的名称 —— 文献标题
现在浏览器中用"选择元素" (Ctrl + Shift + C) 对标题定位一波,然后使用XPath选择器筛选出标题文本 (CSS选择器类似):$x('//div[@id="citation"]/i/text()')[0]
,然后笔者写好代码套用该选择器时,发现某些文献标题并没有
标签,所以需要加判断 (比如零长度等等)
修改我们在 3.2.2 节写的 scraping_using_lxml.py
,之前我们只返回了一个 onclick
中的下载链接,我们现在要额外返回一个标题名称,使用字典是一个不错的选择:
from lxml. html import fromstring
import re
def get_link_cssselect ( html) :
try :
tree = fromstring( html)
a = tree. cssselect( 'div#buttons > ul > li > a' ) [ 0 ]
onclick = a. get( 'onclick' )
title = tree. cssselect( 'div#menu > div#citation > i' )
if len ( title) == 0 :
title = tree. cssselect( 'div#menu > div#citation' )
title = title[ 0 ] . text
onclick = re. findall( r"location.href\s*=\s*'(.*?)'" , onclick) [ 0 ]
return {
'title' : title, 'onclick' : onclick}
except Exception as e:
print ( 'error occurred: ' , e)
return None
def get_link_xpath ( html) :
try :
tree = fromstring( html)
a = tree. xpath( '//div[@id="butdtons"]/ul/li/a' ) [ 0 ]
onclick = a. get( 'onclick' )
onclick = re. findall( r"location.href\s*=\s*'(.*?)'" , onclick) [ 0 ]
title = tree. xpath( '//div[@id="citation"]/i/text()' )
if len ( title) == 0 :
title = tree. xpath( '//div[@id="citation"]/text()' )
return {
'title' : title[ 0 ] , 'onclick' : onclick}
except Exception as e:
print ( 'error occurred: ' , e)
return None
对代码的说明:
笔者后续实践中发现,不是所有文献标题都有
标签,因此需要加个判断,即当匹配列表为空时,匹配其父级内容作为标题
注意改动的部分 (后面加注了数字)
正则表达式和 Beautiful Soup也采用相似的修改方式,只需要多抓取一个标题名称就行,笔者在此仅给出主要修改处的代码,其余保持不变:
def get_link_using_bs4 ( html, parser= 'html5lib' ) :
try :
. . .
except :
. . .
try :
div = soup. find( 'div' , attrs= {
'id' : 'buttons' } )
if div:
a = div. find( 'a' , attrs= {
'href' : '#' } )
if a:
a = a. attrs[ 'onclick' ]
onclick = re. findall( r"location.href\s*=\s*'(.*?)'" , a) [ 0 ]
div = soup. find( 'div' , attrs= {
'id' : 'citation' } )
title = div. find( 'i' )
if title:
title = title. get_text( )
else :
title = div. get_text( )
return {
'title' : title, 'onclick' : onclick}
except Exception as e:
print ( 'error occured: ' , e)
return None
def get_links ( pattern, html) :
. . .
def get_link_using_regex ( html) :
pattern_onclick = '''\s*
\s*.*?\s*]+href\s*=\s*#\s*onclick\s*=\s*"location.href='(.*?)'">'''
pattern_title = ''']+>(.*?)
'''
try :
title = get_links( pattern_title, html) [ 0 ]
if title:
i = get_links( '(.*?) ' , title)
title = i[ 0 ] if i else title
onclick = get_links( pattern_onclick, html) [ 0 ]
if onclick and title:
return {
'title' : title, 'onclick' : onclick}
elif onclick:
print ( 'No title, now use onclick string to be the title.' )
return {
'title' : onclick, 'onclick' : onclick}
except Exception as e:
print ( 'error occurred: ' , e)
return None
if __name__ == '__main__' :
from download import download
from download import doi_parser
dois = [ '10.1016/j.apergo.2020.103286' ,
'10.1016/j.jallcom.2020.156728' ,
'10.3964/j.issn.1000-0593(2020)05-1356-06' ]
links = [ ]
for doi in dois:
url = doi_parser( doi, 'sci-hub.do' )
html = download( url, headers= {
'User-Agent' : 'sheng' } )
link = get_link_using_regex( html)
if link:
links. append( link)
for link in links:
print ( link)
robots.txt 解析以及下载时间间隔设置
还记得 2.3.2 节提到的 robots.txt
吗?它是我们进行爬虫前的一个参考,为了降低爬虫被封禁的风险,我们需要遵守其中的约束,可以在 网站域名 + /robots.txt
查看文件要求,我们在 2.3.2 节已经初步分析过了,只要我们的用户代理不是 Twitterbot 并且不以它为子串,那么就没有限制。尽管如此,我们还是可以设置一个下载的间隔时间,并且在发送请求前检查请求是否符合 robots.txt 的规定,这样我们的爬虫便可以适应更多的变化。
我们可以在请求文献内容前进行进行一次 robots.txt
验证,如果验证通过我们再执行下载,并设置下载时间间隔。我们正好借此机会调整一下之前的代码设计,尽可能减少功能之间的耦合 (download.py):
import requests
from urllib. robotparser import RobotFileParser
import time
from urllib. parse import urlparse
from filename import get_valid_filename
def doi_parser ( doi, start_url, useSSL= True ) :
"""Parse doi to url"""
HTTP = 'https' if useSSL else 'http'
url = HTTP + '://{}/{}' . format ( start_url, doi)
return url
def get_robot_parser ( robot_url) :
"""解析robots.txt"""
rp = RobotFileParser( )
rp. set_url( robot_url)
rp. read( )
return rp
"""延时函数"""
def wait ( url, delay= 3 , domains= {
} ) :
"""wait until the interval between two
downloads of the same domain reaches time delay"""
domain = urlparse( url) . netloc
last_accessed = domains. get( domain)
if delay > 0 and last_accessed is not None :
sleep_secs = delay - ( time. time( ) - last_accessed)
if sleep_secs > 0 :
time. sleep( sleep_secs)
domains[ domain] = time. time( )
def download ( url, headers, proxies= None , num_retries= 2 ) :
print ( 'Downloading: ' , url)
try :
resp = requests. get( url, headers= headers, proxies= proxies, verify= False )
html = resp. text
if resp. status_code >= 400 :
print ( 'Download error: ' , resp. text)
html = None
if num_retries and 500 <= resp. status_code < 600 :
return download( url, headers, proxies, num_retries- 1 )
except requests. exceptions. RequestException as e:
print ( 'Download error' , e)
return None
return html
def download_pdf ( result, headers, proxies= None , num_retries= 2 ) :
url = result[ 'onclick' ]
url = 'https:{}' . format ( url)
print ( 'Downloading: ' , url)
try :
resp = requests. get( url, headers= headers, proxies= proxies, verify= False )
if resp. status_code >= 400 :
print ( 'Download error: ' , resp. status_code)
if num_retries and 500 <= resp. status_code < 600 :
return download( result, headers, proxies, num_retries- 1 )
filename = get_valid_filename( result[ 'title' ] ) + '.pdf'
print ( filename)
with open ( filename, 'wb' ) as fp:
fp. write( resp. content)
except requests. exceptions. RequestException as e:
print ( 'Download error' , e)
return False
return True
def sci_hub_crawler ( doi_list, robot_url= None , user_agent= 'sheng' , proxies= None ,
num_retries= 2 , delay= 3 , start_url= 'sci-hub.do' , useSSL= True , get_link= None , nolimit= False ) :
"""
给定文献doi列表,爬取对应文献的 pdf 文件
:param doi_list: doi列表
:param robot_url: robots.txt在sci-bub上的url
:param user_agent: 用户代理,不要设为 'Twitterbot'
:param proxies: 代理
:param num_retries: 下载重试次数
:param delay: 下载间隔时间
:param start_url: sci-hub 主页域名
:param useSSL: 是否开启 SSL,开启后HTTP协议名称为 'https'
:param get_link: 抓取下载链接的函数对象,调用方式 get_link(html) -> html -- 请求的网页文本
所使用的函数在 scraping_using_%s.py % (bs4, lxml, regex) 内
:param nolimit: 是否遵循 robots.txt 的约束,如果为True则不受其限制
:return:
"""
headers = {
'User-Agent' : user_agent}
HTTP = 'https' if useSSL else 'http'
if not get_link:
print ( 'Crawl failed, no get_link method.' )
return None
if not robot_url:
robot_url = HTTP + '://{}/robots.txt' . format ( start_url)
try :
rp = get_robot_parser( robot_url)
except Exception as e:
rp = None
print ( 'get_robot_parser() error: ' , e)
domains= {
}
download_succ_cnt: int = 0
for doi in doi_list:
url = doi_parser( doi, start_url, useSSL)
if rp and rp. can_fetch( user_agent, url) or nolimit:
wait( url, delay, domains)
html = download( url, headers, proxies, num_retries)
result = get_link( html)
if result and download_pdf( result, headers, proxies, num_retries) :
download_succ_cnt += 1
else :
print ( 'Blocked by robots.txt: ' , url)
print ( '%d of total %d pdf success' % ( download_succ_cnt, len ( doi_list) ) )
if __name__ == '__main__' :
from scraping_using_lxml import get_link_xpath, get_link_cssselect
from scraping_using_bs4 import get_link_using_bs4
from scraping_using_regex import get_link_using_regex
from random import choice
dois = [ '10.1016/j.apergo.2020.103286' ,
'10.1016/j.jallcom.2020.156728' ,
'10.3964/j.issn.1000-0593(2020)05-1356-06' ]
get_links_methods = [ get_link_xpath, get_link_cssselect, get_link_using_bs4, get_link_using_regex]
get_link = choice( get_links_methods)
print ( 'use %s as get_link_method.' % get_link. __name__)
print ( 'obey the limits in robots.txt: ' )
sci_hub_crawler( dois, get_link= get_link, user_agent= 'sheng' )
print ( 'no any limit: ' )
sci_hub_crawler( dois, get_link= get_link, user_agent= 'sheng' , nolimit= True )
print ( 'Done.' )
运行结果:
use get_link_xpath as get_link_method.
obey the limits in robots.txt:
Blocked by robots.txt: https://sci-hub.do/10.1016/j.apergo.2020.103286
Blocked by robots.txt: https://sci-hub.do/10.1016/j.jallcom.2020.156728
Blocked by robots.txt: https://sci-hub.do/10.3964/j.issn.1000-0593(2020)05-1356-06
0 of total 3 pdf success
no any limit:
Downloading: https://sci-hub.do/10.1016/j.apergo.2020.103286
Downloading: https://sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true
Implementing Virtual Reality technology for safety training in the precast_ prestressed concrete industry. Applied Ergonomics, 9.pdf
Downloading: https://sci-hub.do/10.1016/j.jallcom.2020.156728
Downloading: https://sci-hub.do/downloads/2020-11-23/ac/tsvinkinberg2021.pdf?download=true
Tsvinkinberg, V. A., Tolkacheva, A. S., Filonova, E. A., Gyrdasova, O. I., Pikalov, S. M., Vorotnikov, V. A., … Pikalova, E. Y. .pdf
Downloading: https://sci-hub.do/10.3964/j.issn.1000-0593(2020)05-1356-06
error occurred: Document is empty
2 of total 3 pdf success
Done.
对代码和结果的说明:
代码可能有点长,但是其中很多函数在之前已经出现过了,大部分函数只作了很小部分的改动:比如,download()
和 download_pdf()
不再传入 doi,而是传入对应的 url。另外,新增了 doi_parser()
转换函数,这样就实现了解耦,能让 download()
具有通用性;新增的 get_robot_parser()
以及 can_fetch()
函数实现了 robots.txt 的解析,并遵循其中的约束; wait()
函数设置了下载间隔时间
sci_hub_crawler()
集成了"根据给定的 DOI 列表批量(串行)爬取对应的 pdf 文件"的功能,其参数列表的说明在函数开头标注了。如此一来,当我们爬取到 doi 列表后,只需要调用 sci_hub_crawler()
并睡上一觉就行了
主函数中我们使用了一个 get_links_methods
列表存储了所有抓取方法,然后使用 random.choice()
(伪)随机选取了其中一个,传给了 sci_hub_crawler()
的 get_link
参数,这其实就是多态性的一种体现 —— 同一种调用(get_link(html)
),不一样的方法。这类似于 C# 中的委托 (delegate) 或是 C/C++ 的函数指针。但要求 get_link_methods
中的所有函数参数列表一致,从实用性来看,要保证非默认参数的个数和顺序相同。
从运行结果来看,似乎 robots.txt 中的约束比我们之前解读的要强很多 —— 它不允许我们爬取资源,但我们仍然可以 “知不可为而为之” (设置 nolimit=True
)。只不过为了降低可能的封禁隐患,我们可以让下载间隔大一些(比如 5 - 10s,这样按照两分钟一个的速率一夜也能爬个200+文件,这够多了)。不过也别高估了这个 sci_hub_crawler()
,它就是个串行爬虫,想让服务器崩溃可没那么容易,所以我们还是可以放心爬 (大不了就是几天的封禁嘛)
添加一个简单的缓存类 Cache
有时候我们可能因为不可抗力 (比如断网、死机等) 而不得不中止我们的爬取,设想这样一个情况:我们要爬取1000个文件,然而我们在下载到第501个文件时出现了上述的意外,当一切恢复正常后,我们想要继续从第 501 个文件处开始下载,怎么办? 一种极简的方法是:设立一个变量以记录我们当前已经成功下载的文件个数,并且每当一个文件下载成功时,将此变量写入一个文件 (比如 .txt),重启下载时读取该变量值,从它的下一个序号开始下载即可。
以上方法适用于我们的 DOI 列表项顺序不变的情况,事实上对于我们这个小项目来说已经满足要求了;但还有一种普适性更强的方法,那就是按键值对存储 已经下载的资源标识,这里我们可以选用 {文献url: pdf_url} 作为资源标识,借用 Python3 标准库中的 json 模块来实现缓存数据加载和存储。
由于我们的缓存在内存中以字典形式存储,与此同时需要访问外存,进行缓存读写 ,我们可以将缓存功能封装在一个类 中,并通过特殊成员函数 __getitem__()
和 __setitem__()
使得类对象的操作行为类似于字典对象。
下面构建一个 Cache 类 (cache.py):
import json
import os
class Cache :
def __init__ ( self, cache_dir) :
self. cache_dir = cache_dir
self. cache = self. read_cache( )
def __getitem__ ( self, url) :
if self. cache. get( url) :
return self. cache[ url]
else :
return None
def __setitem__ ( self, key, value) :
"""将{url: pdf_url} 追加到字典中,并写入外存"""
filename = self. cache_dir
self. cache[ key] = value
if os. path. exists( filename) :
with open ( filename, 'r' ) as fp:
if os. path. getsize( filename) :
cache = json. load( fp)
else :
cache = {
}
cache. update( {
key: value} )
with open ( filename, 'w' ) as fp:
json. dump( cache, fp, indent= 0 )
def read_cache ( self) :
"""加载json数据成为Python字典对象,至少也是个空字典"""
try :
filename = self. cache_dir
if os. path. exists( filename) :
if os. path. getsize( filename) :
with open ( filename, 'r' , encoding= 'utf-8' ) as fp:
return json. load( fp)
else :
return {
}
else :
with open ( filename, 'w' , encoding= 'utf-8' ) :
return {
}
except Exception as e:
print ( 'read_cache() error: ' , e)
return {
}
要使用此类,我们得修改 sci_hub_crawler(),下面仅展示更改的代码 (download.py):
def sci_hub_crawler ( doi_list, robot_url= None , user_agent= 'sheng' , proxies= None , num_retries= 2 ,
delay= 3 , start_url= 'sci-hub.do' , useSSL= True , get_link= None , nolimit= False , cache= None ) :
"""
...
:param cache: 应传入一个缓存类对象,在此代码块中我们应把它当作字典使用
...
"""
. . .
try :
. . .
for doi in doi_list:
. . .
if cache and cache[ url] :
print ( 'already downloaded: ' , cache[ url] )
download_succ_cnt += 1
continue
if rp and rp. can_fetch( user_agent, url) or nolimit:
. . .
if result and download_pdf( result, headers, proxies, num_retries) :
if cache:
cache[ url] = 'https:{}' . format ( result[ 'onclick' ] )
. . .
正如函数开头注释所说,虽然cache是个Cache类的对象,但是由于类中的特殊函数(上文已经提及),实现了运算符重载,我们可以像使用字典一样使用它。
下面我们可以简单测试一下新增的缓存功能 (cache.py):
if __name__ == '__main__' :
from download import sci_hub_crawler
from scraping_using_lxml import get_link_xpath
cache_dir = './cache.txt'
dois = [ '10.1016/j.apergo.2020.103286' ,
'10.1016/j.jallcom.2020.156728' ,
'10.3964/j.issn.1000-0593(2020)05-1356-06' ]
sci_hub_crawler( dois, get_link= get_link_xpath, user_agent= 'sheng' , nolimit= True , cache= Cache( cache_dir) )
print ( 'Done.' )
运行结果与说明:
初次运行,文件下载符合预期,并且在代码的同级目录下生成了 cache.txt 文件,内容如下:
{
"https://sci-hub.do/10.1016/j.apergo.2020.103286": "https://sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true",
"https://sci-hub.do/10.1016/j.jallcom.2020.156728": "https://sci-hub.do/downloads/2020-11-23/ac/tsvinkinberg2021.pdf?download=true"
}
上述内容加载到内存后是一个 Python 字典,键是 sci-hub 上输入 doi 后搜索所得页面的 url,值是相应 pdf 资源的 url
第二次运行,由于文献已经下载过了,除了第三个异常的链接外,其余文献将不再执行下载,而是给出"已经下载"的提示:
already downloaded: https://sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true
already downloaded: https://sci-hub.do/downloads/2020-11-23/ac/tsvinkinberg2021.pdf?download=true
Downloading: https://sci-hub.do/10.3964/j.issn.1000-0593(2020)05-1356-06
error occurred: Document is empty
2 of total 3 pdf success
Done.
虽然还存在很多可以改进的地方,但现在是时候打住了,现在的版本已经符合要求了 (再优化就写不完了)。
3.3 回到 Web of Science,提取搜索页的 DOI 列表
至此,我们已经翻过了最高的山,剩余工作很简单 —— 抓取 DOI 就完事了,它与 3.2 节的不同之处:
网站不同,意味着元素选择会有所改变
只有文本抓取,没有二进制数据流的下载过程
是不是非常简单?我们要做的是抓取 html 文本中的 DOI,然后用列表存起来,还可以把它写入磁盘。这些操作我们在 3.2 节已经见过了。但是 —— 你怎么获得搜索结果中所有的文献链接呢 (搜索结果往往分布在多个分页里) ?其实我们在 2.1 节已经讨论过了这个问题,并且给出了解决方案,笔者在此画个示意图,展现一下 Web of Science 搜索结果的层级结构:
该示意图其实展示了一个比较通用的爬虫模型 —— 链接爬虫 (Link Crawler),它可以通过一个源链接,跟踪页面中的其他链接,使得爬虫表现得更像普通用户 [1] ,降低封禁风险。(一页页地浏览,并且按顺序访问文献,的确符合 “普通用户” 的行为)
笔者这里不再对链接爬虫作过多展开 ,原因有二:其一,笔者爬取 Web of Science 的过程中没被封禁过 ,而且也没找到该网站的 robots.txt,再加上这是串行爬取,访问时也就省去了普通用户的控件操作时间,对网站服务器的负载贡献不大;其二,这种方法相对较慢,为了获取 doi,它还需要额外花时间爬取链接。
事实上,针对这个网站有更高效的方法 。
2020-12-11 晴
3.3.1 方法一:修改 doc 属性值快速构建 url,然后从中爬取 doi
这是笔者点进一篇文献的网站,观察 url 链接发现的一种方法,此方法不需要用到分页,可以直接获取每个文献链接 。我们看一下第 1 页第 1 篇 文献的 url:
http://apps.webofknowledge.com/full_record.do?product=UA&search_mode=
GeneralSearch&qid=30&SID=8FZeNUIigweW9fYyFJn&page=1&doc=1
试着解析一下这个 url:
http://apps.webofknowledge.com/
→ \rightarrow → Web of Science 主页链接
xxx.do
→ \rightarrow → 是个网页后台程序,刚点搜索弹出的页面便是 Search.do
、切换分页时为 Summary.do
,打开具体某一文献时为 full_record.do
?attr1=value1&attr2=value2&...
→ \rightarrow → 问号后面接一个或多个用 &
分隔开来的变量,并设定一定值,从而实现动态链接,也就是说对于不同的属性以及属性值,会生成不同的网页。那我们来看看上面那个链接跟了些啥参数吧:
product
。这个不用管,所有页面都一样
search_mode
。 看名字就是知道,指搜索模式,这个也不用管
qid
、SID
。不知道啥意思,但不可少,而且同一个搜索结果下所有文献网站的 qid
和 SID
都一样,所以我们保持原样即可
page
。分页号码,对应的便是不同分页,可能有用。(但事实上这种分页与结果列表本身没有关系,只是刻意限定了每页的结果数目而已,所以很可能也不用管)
doc
。文献搜索序号 ,与分页号无关,当 page 和 doc 共存时 doc “说得算”,你会发现即使没有分页号也能正常打开目标网页,说明分页号page不重要,重要的是文献搜索序列号doc !!!
如此一来,我们得到了如下三个子问题,并且都很好解决:
url 转换,给定一个搜索结果源链接 (必须是文献链接而不是搜索页或分页链接 ),其格式为 http://apps.webofknowledge.com/full_record.do?attr1=value1&attr2=...&attrn=valuen&doc=num
, 要获取搜索列表中第 i i i 篇文献网页,将 url 末尾参数 doc 改变,使得 &doc=i 即可,此时链接变为:http://apps.webofknowledge.com/full_record.do?attr1=value1&attr2=...&attrn=valuen&doc=i
抓取搜索结果总数:21,322 ,注意搜索结果总数中的逗号,要把它转变为整数。不过,笔者偷下懒,把这项任务交给用户,人眼"识别"结果总数,并传到接口的相应参数中。
抓取文献网页中的 DOI:标签特征在 2.2 节已经解析过,只需采用 3.2.2 节的一种抓取方法即可 (笔者使用XPath选择器:'//span[text()="DOI:"]/following::*[1]')[0].text()
)
< p class = " FR_field" >
< span class = " FR_label" > DOI: span>
< value> 10.1016/j.electacta.2020.137142 value>
p>
下面是笔者 抓取一定数目搜索结果的 DOI 并构成列表 的 Python3 实现 (doi_crawler.py):
from download import download
import re
from lxml. html import fromstring
def url_changer ( source_url) :
"""获取文献网站url的模式"""
url = re. findall( r'''(.*)&doc''' , source_url) [ 0 ]
doc = '&doc='
return url + doc
def get_doi ( html) :
"""根据获取到的html获得其中的doi并返回"""
try :
tree = fromstring( html)
doi = tree. xpath( '//span[text()="DOI:"]/following::*[1]' ) [ 0 ] . text
return doi
except Exception as e:
print ( 'get_doi() error: ' , e)
return None
def doi_crawler ( pattern_url, headers= None , number= 500 ) :
""" 获得搜索结果中第 [1, number] 的 doi
pass the following parameter
:param pattern_url: 搜索结果内任意一篇文献的url,不是分页或者搜索结果页的!
:param number: doi获取数目,不要超过页面最大结果数
"""
if headers is None :
headers = {
'User-Agent' : 'sheng' }
base_url = url_changer( pattern_url)
dois = [ ]
for i in range ( 1 , number + 1 ) :
url = base_url + str ( i)
html = download( url, headers)
doi = get_doi( html)
if doi:
dois. append( doi)
return dois
def save_doi_list ( dois, filename) :
"""将doi列表项以[filename].txt保存到当前文件夹中,"""
filepath = filename[ : 128 ] + '.txt'
try :
with open ( filepath, 'w' ) as fp:
for doi in dois:
fp. writelines( doi + '\n' )
except Exception as e:
print ( 'save error: ' , e)
def read_dois_from_disk ( filename) :
"""从磁盘文件[filename].txt中
按行读取doi,返回一个doi列表"""
dois = [ ]
try :
filepath = filename + '.txt'
with open ( filepath, 'r' ) as fp:
lines = fp. readlines( )
for line in lines:
dois. append( line. strip( '\n' ) )
return dois
except Exception as e:
print ( 'read error: ' , e)
return None
if __name__ == '__main__' :
import time
source_url = 'http://apps.webofknowledge.com/full_record.do?product=UA&' \
'search_mode=GeneralSearch&qid=2&SID=6F9FiowVadibIcYJShe&page=1&doc=2'
start = time. time( )
dois = doi_crawler( source_url, number= 10 )
save_doi_list( dois, 'dois' )
print ( 'time spent: %ds' % ( time. time( ) - start) )
print ( 'now read the dois from disk: ' )
doi_list = read_dois_from_disk( 'dois' )
for doi in doi_list:
print ( doi)
运行结果:
Downloading: http://apps.webofknowledge.com/full_record.do?...&doc=1
...
Downloading: http://apps.webofknowledge.com/full_record.do?...&doc=10
time spent: 9s
now read the dois from disk:
10.1016/j.apcatb.2020.119553
...
10.1016/j.ceramint.2020.08.241
对代码与结果的说明:
虽然有很多函数,但函数结构非常简单,而且对函数参数和功能作了注释,就不过多解读了。读者可以从主函数片段中得知主要的函数接口 (有 3 个,分别是 doi_crawler() 、save_doi_list() 、 read_dois_from_disk() )
XPath选择器的构造参考了一位博主的博客 [2] ,链接:XPath 选取具有特定文本值的节点
笔者仅选取了搜索结果前 10 项,测试了多次,耗时在 9 - 15s 范围内,也就是大约 1 秒 1 个 doi,不知道读者能否接受这个速度呢 (反正笔者感觉还可以)
爬虫受网络因素影响,偶尔会爬取失败,重试几次就好了 (sci_hub_crawler() 也一样)
与 3.2 节的 sci_hub_crawler() 不同,本节给用户留了两个小任务:① 提供一个文献的链接;② 设定最大 doi 个数
3.3.2 方法二:结合 Web of Science 导出功能的"零封禁几率"方法
上面的方法虽然通过找规律的方式省去了爬取文献链接的过程,提高了效率,但并不能保证在进行大量爬取时会免受封禁。好在 Web of Science 网站提供了一个便利的功能 —— 导出选择的选项 。它通过一个按钮控件的点击事件触发,如下图所示:
笔者以 Unity3D 为主题,试着导出第 1 至 500 项,以 html 的格式保存文献数据。然后我们打开此文件 (savedrecs.html,即 save document records),找寻 DOI 所在位置, 如下所示:
根据笔者观察,几乎每一个 标签的属性 valign
值是一致的,那么我们就只能根据文本 “DI” 来定位并选择其下一个兄弟元素 的方式来获取目标 DOI 了。因此,笔者采用 XPath 选择器,使用的选择字符串与 3.3.1 节类似。
下面给出 根据导出的 html 记录,抓取其中的 DOI 并返回列表 的 Python3 实现 (advanced_doi_crawler.py):
from lxml. html import fromstring
def get_doi ( html) :
"""根据获取到的html获得其中的doi并返回"""
results = [ ]
try :
tree = fromstring( html)
dois = tree. xpath( '//td[text()="DI "]/following::*[1]' )
for doi in dois:
results. append( doi. text)
return results
except Exception as e:
print ( 'get_doi() error: ' , e)
return None
def doi_crawler ( filepath) :
"""html 导出文件的路径"""
try :
with open ( filepath, 'r' , encoding= 'utf-8' ) as fp:
html = fp. read( )
doi_list = get_doi( html)
return doi_list
except Exception as e:
print ( 'doi_crawler() error' , e)
return None
if __name__ == '__main__' :
import time
start = time. time( )
filepath = './data.html'
doi_list = doi_crawler( filepath)
print ( 'time spent: %ds' % ( time. time( ) - start) )
print ( '%d doi records in total: ' % len ( doi_list) )
for doi in doi_list:
print ( doi)
print ( 'Done.' )
运行结果:
time spent: 0s
206 doi records in total:
10.1016/j.apergo.2020.103286 #1
10.11607/ijp.6835 #2
...
10.1016/j.proeng.2017.10.509 #206
Done.
对代码与结果的说明:
xpath() 返回的是一个列表,只不过之前的实践我们经常只要其中第一项,这里存在多个匹配,所以我们全都要
doi_crawler() 中的文件读取用的是 read(),而不是用 readlines()。前者一次读取完;后者读取所有行,保存在一个列表中 [3]
从结果看,500 条记录中仅仅爬取了 206 个 DOI,这是正常的 —— Unity3D 的很多成果都是以会议形式发表的;
与 3.3.1 的方法相比,两种方法的时间开销完全不是一个级别的 —— 此方法 1s 之内即可完成;如果换作之前的方法,耗时将近 10 min
STEP4 组装起来,形成终极接口:sci_spider()
我们分别用 3.2 节 和 3.3 节 制作了 sci_hub_crawler() 和 doi_crawler() (笔者用 3.3.2 节的),并作了简单的测试,至少现在没看到问题。那么把它们组合起来会不会引入新问题呢?实践一下就知道了!
笔者先在此列出待调用函数的函数原型,作为流程梳理的参考:
def doi_crawler ( filepath) :
pass
def sci_hub_crawler ( doi_list, robot_url= None , user_agent= 'sheng' , proxies= None , num_retries= 2 ,
delay= 3 , start_url= 'sci-hub.do' , useSSL= True , get_link= None , nolimit= False , cache= None ) :
pass
def get_link_xpath ( html) :
pass
注:尽管笔者可能在某一步骤使用了多种方法来实现,但此处笔者只选择一种方案,其余方案就不再展示实现方法了,但思路都是一致的。具体来说,笔者抓取标签内容使用的是 XPath 选择器;"获取 DOI 列表"采用的是 3.3.2 节的方法。
4.1 流程梳理 ⋆ \star ⋆
本节实际上是本爬虫的使用说明书 。
打开 Web of Science,搜索感兴趣的内容,得到一个搜索结果列表
点击 “导出为其他文件格式 ” 按钮,记录条数自选,记录内容 为作者、标题、来源出版物 ,文件格式 选择HTML ,然后点击"导出",记录该 html 文件的 绝对路径 filepath
(也可以是相对路径)
调用 doi_crawler(filepath) ,返回一个 doi 列表,将之命名为 doi_list
调用 sci_hub_crawler(doi_list, get_link=get_link_xpath, nolimit=True, cache=Cache(cache_dir)) ,如果不需要缓存,可以不传参至 cache
。另外说明的是,cache_dir
是缓存文件的路径,一般用相对路径 即可;其余参数根据需要来调整
睡上一觉,等待结果
4.2 组装起来,给它取个名字,就叫 “sci_spider” 好了
上面流程已经说的很清楚了,组装起来不是什么难事,但需要注意:组装的这些函数的参数列表需要合理地合并。
下面就是笔者组装的情况 (sci_spider.py):
from download import sci_hub_crawler
from scraping_using_lxml import get_link_xpath
from cache import Cache
from advanced_doi_crawler import doi_crawler
def sci_spider ( savedrec_html_filepath, robot_url= None , user_agent= 'sheng' , proxies= None , num_retries= 2 ,
delay= 3 , start_url= 'sci-hub.do' , useSSL= True , get_link= get_link_xpath,
nolimit= False , cache= None ) :
"""
给定一个文献索引导出文件 (来自 Web of Science),(按照DOI)下载文献对应的 pdf文件 (来自 sci-hub)
:param savedrec_html_filepath: 搜索结果的导出文件 (.html),其中含有文献记录 (每一条记录可能有doi,也可能没有)
:param robot_url: robots.txt在sci-bub上的url
:param user_agent: 用户代理,不要设为 'Twitterbot'
:param proxies: 代理
:param num_retries: 下载重试次数
:param delay: 下载间隔时间
:param start_url: sci-hub 主页域名
:param useSSL: 是否开启 SSL,开启后HTTP协议名称为 'https'
:param get_link: 抓取下载链接的函数对象,调用方式 get_link(html) -> html -- 请求的网页文本
所使用的函数在 scraping_using_%s.py % (bs4, lxml, regex) 内,默认用xpath选择器
:param nolimit: do not be limited by robots.txt if True
:param cache: 一个缓存类对象,在此代码块中我们完全把它当作字典使用
"""
print ( 'trying to collect the doi list...' )
doi_list = doi_crawler( savedrec_html_filepath)
if not doi_list:
print ( 'doi list is empty, crawl aborted...' )
else :
print ( 'doi_crawler process succeed.' )
print ( 'now trying to download the pdf files from sci-hub...' )
sci_hub_crawler( doi_list, robot_url, user_agent, proxies, num_retries, delay, start_url,
useSSL, get_link, nolimit, cache)
print ( 'Done.' )
if __name__ == '__main__' :
filepath = './data.html'
cache_dir = './cache.txt'
cache = Cache( cache_dir)
sci_spider( filepath, nolimit= True , cache= cache)
4.3 对第一次运行结果的分析与问题处理 ⋆ \star ⋆
我们运行 sci_spider.py 中的主函数代码,结束后对结果进行分析。
4.3.1 运行结果分析
之前也看到了,一共有 206 个 DOI,这个下载量比较大了,检查无误后,我们现在尝试运行一下:
trying to collect the doi list...
doi_crawler process succeed.
now trying to download the pdf files from sci-hub...
... # 下载过程省略
94 of total 206 pdf success
Done.
time spent: 1664s
我们可以轻松地从运行结果中提取以下数据:
206 个 doi 中下载成功的有 94 个,占比 45.6%
总共用时为 1664 秒,即 27 分 44 秒,成功下载单个文件的用时为 17.7 秒
另外,我们看看磁盘上的变化:
cache.txt
我们注意到最后一个 url 对应第 95 行,而第一个文件从第 2 行开始,所以一共有 94 个 pdf 文件成功下载,从数量上看这是没有错的。
pdf 文件目录
惊了,明明成功下载了 94 个,却只有 71 个项目 ,难道被谁吃了吗?确实,看看第一个文件名称 —— 一个下划线,这暗示着有些文献没抓到标题,标题为空字符,然后这个仅有的空字符被替换成了下划线 ,从数目看,空标题的情况有 24 个,数量占比不小了,所以我们得对这些情况下的 html 文本再度分析一下。在此之前,我们再仔细看看运行窗口中那些下载失败或标题为空的文件对应的输出信息吧:
# 第一种类型的错误:找不到合适的代理 (不管了)
Downloading: https://sci-hub.do/10.11607/ijp.6835
error occurred: list index out of range
...
# 第二种类型的错误:文件标题抓取为空 (重点关注)
Downloading: https://sci-hub.do/10.3390/s20205967
Downloading: https://sci-hub.do/downloads/2020-10-31/dc/[email protected] ?download=true
_.pdf
...
# 第三种类型的错误:文献网页内容为空 (不管了)
Downloading: https://sci-hub.do/10.3275/j.cnki.lykxyj.2020.03.013
error occurred: Document is empty
...
# 第四种类型的错误:原链接中已有HTTP协议头 (重点关注)
Downloading: https://sci-hub.do/10.1109/TCIAIG.2017.2755699
Downloading: https:https://twin.sci-hub.do/6601/f481261096492fa7c387e58b490c15c6/llobera2017.pdf?download=true
Download error No connection adapters were found for
'https:https://twin.sci-hub.do/6601/f481261096492fa7c387e58b490c15c6/llobera2017.pdf?download=true'
...
# 第五种类型的错误 :IP被 ACM DL(美国计算机学会 数字图书馆) 官网封禁了,
# 但似乎是因为前面加了个 sci-hub 的缘故,去掉后还是可以正常访问 ACM DL (不管了)
Downloading: https://sci-hub.do/10.1145/3337722.3341860
Download error:
504 Gateway Time-out
504 Gateway Time-out
openresty/1.19.3.1
# 第六种类型的错误:重定向至一个韩国的文献网站,还要登录啥的,下不了无所谓了 (不管了)
Downloading: https://sci-hub.do/10.5626/JOK.2019.46.11.1157
Download error:
Ошибка: не удалось открыть страницу # 错误:无法打开一页
...
error occurred: expected string or bytes-like object
# 第七种类型的错误:重定向至一个法国的文献网站,下不了无所谓了 (不管了)
Downloading: https://sci-hub.do/10.16923/reb.v16i1.730
error occurred: Unicode strings with encoding declaration are not supported.
Please use bytes input or XML fragments without declaration.
笔者发现了七种不同类型的错误信息输出 (包括空标题),上述出错的 url 笔者都一一点开过,对于更详细的错误信息,笔者已经在上面作了注释。下面重点关注两个比较容易纠正且比较普遍的错误:
错误类型二:标题抓取为空
错误类型四:HTTP协议头重复
下面和笔者一起逐个解决~
4.3.2 问题1:标题抓取为空 —— 用 DOI 作为名字
点开文献网址,页数有点多,加载片刻后如下图:
如你所见,我们的目标区域内容为空,那我们得想想别的办法了?即使不能保证有意义,但最起码得给它个不一样的名字,免得造成文件覆盖而丢失,那我们最容易想到的就是用 DOI 作为名字啦。
代码做以下调整:
download.py
def download_pdf ( result, headers, proxies= None , num_retries= 2 , doi= None ) :
···
try :
. . .
if len ( result[ 'title' ] ) < 5 :
filename = get_valid_filename( doi) + '.pdf'
else :
filename = get_valid_filename( result[ 'title' ] ) + '.pdf'
. . .
def sci_hub_crawler ( doi_list, robot_url= None , user_agent= 'sheng' , proxies= None , num_retries= 2 ,
delay= 3 , start_url= 'sci-hub.do' , useSSL= True , get_link= None , nolimit= False , cache= None ) :
. . .
if result and download_pdf( result, headers, proxies, num_retries, doi) :
. . .
. . .
笔者假定字符数小于 5 时就采用 doi 命名。 (标题至少也得 5 个字符吧)
4.3.3 问题2:HTTP协议头重复 —— 添加判断去重
点进去一看,有些 onclick 内容中的链接自带HTTP协议头:
< a href = " #" onclick = " location.href=
' https://twin.sci-hub.do/6601/f481261096492fa7c387e58b490c15c6/llobera2017.pdf?download=true' " >
⇣ save a>
为此我们需要在代码中添加一层判断,首先看看有无HTTP协议头,如果没有才添加,修改的代码如下 (download.py):
def download_pdf ( result, headers, proxies= None , num_retries= 2 , doi= None ) :
url = result[ 'onclick' ]
components = urlparse( url)
if len ( components. scheme) == 0 :
url = 'https:{}' . format ( url)
print ( 'Downloading: ' , url)
. . .
if __name__ == '__main__' :
from scraping_using_lxml import get_link_xpath
dois = [ '10.1109/TCIAIG.2017.2755699' ,
'10.3390/s20205967' ,
'10.1016/j.apergo.2020.103286'
]
get_link = get_link_xpath
sci_hub_crawler( dois, get_link = get_link, user_agent= 'sheng' , nolimit= True )
print ( 'Done.' )
运行结果:
Downloading: https://sci-hub.do/10.1109/TCIAIG.2017.2755699
Downloading: https://twin.sci-hub.do/6601/f481261096492fa7c387e58b490c15c6/llobera2017.pdf?download=true
A_tool_to_design_interactive_characters_based_on_embodied_cognition.
_IEEE_Transactions_on_Computational_Intelligence_and_AI_in_G.pdf
Downloading: https://sci-hub.do/10.3390/s20205967
Downloading: https://sci-hub.do/downloads/2020-10-31/dc/[email protected] ?download=true
10.3390_s20205967.pdf
Downloading: https://sci-hub.do/10.1016/j.apergo.2020.103286
Downloading: https://sci-hub.do/downloads/2020-12-01/29/joshi2021.pdf?download=true
Implementing_Virtual_Reality_technology_for_safety_training_in_the_precast__
prestressed_concrete_industry._Applied_Ergonomics,_9.pdf
3 of total 3 pdf success
Done.
从运行结果可见,上述问题都已经修复,而且没有带来额外的问题 (至少看起来是这样)。
4.3.4 最后的调试
下面我们删去 cache.txt 和 下载的 pdf (只是测试用的,不要舍不得),再度运行 sci_spider.py,休息半个小时后看看结果:
trying to collect the doi list...
doi_crawler process succeed.
now trying to download the pdf files from sci-hub...
...
150 of total 206 pdf success
Done.
time spent: 2847s
现在再看看数据,芜湖,起飞 ~ :
206 个 doi 中下载成功的有 150 个,占比 72.8%
总共用时为 2847 秒,即 47 分 27 秒,成功下载单个文件的用时为 18.98 秒
笔者再此基础上再运行了一次程序,用以测试缓存功能是否能正常运行,结果符合我们的预期:
trying to collect the doi list...
doi_crawler process succeed.
now trying to download the pdf files from sci-hub...
...
already downloaded: https:https://twin.sci-hub.do/
6634/7e804814554806b27952fd2974ae4ba1/radionova2017.pdf?download=true
150 of total 206 pdf success
Done.
time spent: 1367s
至此项目结束。
爬虫感想
笔者这次就分享这么多了,一共用了 6 天时间,一边学,一边写博客,一边码代码,花的时间比较长了。文章的长度远远超出我的预期,很多东西也就是顺着思路写的,没怎么整理,笔者想尽可能地还原这个从零到一的过程,不知各位读者觉得笔者是否做到了呢?
笔者写的这个爬虫十分简陋,涉及的爬虫知识也比较浅,爬虫中对于一些问题的处理也很粗糙,但至少还算能正常工作,可以满足一定程度的需求。毕竟,笔者接触爬虫也就是最近几个星期,实践过程中也从各个渠道学到了很多相关的知识,于个人而言已经很满足了。
其实在项目执行初期,笔者还有几个更大的想法,比如,并行下载、将缓存数据存至数据库 (redis) 、可视化下载进度、做个窗体程序等。但限于时间和篇幅,笔者在此都没有实现。另外,笔者发现,很多一开始的想法 (在 STEP1 和 STEP2 中提到的),可能到后面都用不上,其中原因的大多是当初调研时考虑不周全。但是,有谁能保证做一个从没做过的项目时能够预先进行完美设计呢?完美设计与否,最终还是要靠实践来检验和打磨,代码从简单到复杂,再又回到另一个境界的简单。
笔者起初打死都想不到,终极接口 sci_spider()
竟然有如此多的参数,看起来相当复杂;但是,它是笔者实践过程中一步步搭建与优化得到的,就算某个代码细节忘记了,也有办法通过重新回顾此代码而迅速拾起,这或许就是实践与没有实践过的区别。
笔者是一个无语言论者 (虽然用 C++ 和 C# 比较多),但通过这次实践,笔者真切感受到了 Python3 的优雅与强大 —— 它将我们从繁杂的语言细节中解放出来,让我们能集中精力处理去思考问题本身的解决方案。当然,也不能一味地依赖语言带来的强大功能,对于很多底层原理与细节,如有时间也应该去好好琢磨一下。
好了,这段爬虫之旅到此就要画上句号了。笔者做这个项目的初衷就是为了品尝用技术解决具体问题的喜悦,现在确实很满足。然而,凡事都有个主次,笔者还有很多优先级更高的学业任务需要完成,所以可能会有一段时间不碰爬虫,很高兴能分享我的实践过程,也真心希望这些文字能给您带来帮助~
资源 (见GitHub)
笔者已经把本次实践的代码上传到 GitHub 上了,仅供学习用。如果各位只是想要使用的话,可以在 GitHub 上找到更好的爬虫。笔者这个用到的知识很少,功能也很简单,比较 low。
点此访问笔者的 GitHub 资源
References
笔者把主要的参考文献放在这里 (有些在文献中给出了链接),有需要的可以自行查阅。
[1] [德] Katharine Jarmul, 等.用Python写网络爬虫(第二版)[M].李斌, 译.北京:人民邮电出版社, 2018, pp. 1-78
[2] 知否知否呀.XPath 选取具有特定文本值的节点[EB/OL].https://blog.csdn.net/lengchun10/article/details/41044119, 2014-11-12.
[3] 假装自己是小白.Python中read()、readline()和readlines()三者间的区别和用法[EB/OL].https://www.cnblogs.com/yun1108/p/8967334.html, 2018-04-28.
[4] dcpeng.手把手教你如何在Pycharm中加载和使用虚拟环境[EB/OL].https://www.cnblogs.com/dcpeng/p/12257331.html, 2020-02-03.
[5] PilgrimHui.conda环境管理[EB/OL].https://www.cnblogs.com/liaohuiqiang/p/9380417.html, 2018-07-28.
[6] 奔跑中的兔子.爬虫之robots.txt[EB/OL].https://www.cnblogs.com/benpao1314/p/11352276.html, 2019-08-14.
欢迎读者朋友留言。 如有错误请务必批评指正,笔者在此给大佬们抱拳了~
你可能感兴趣的:(Python3,网络爬虫,开放性实验,网络爬虫,Python3,sci-hub,文献爬取,手把手系列)
【一起学Rust | 设计模式】习惯语法——使用借用类型作为参数、格式化拼接字符串、构造函数
广龙宇
一起学Rust # Rust设计模式 rust 设计模式 开发语言
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、使用借用类型作为参数二、格式化拼接字符串三、使用构造函数总结前言Rust不是传统的面向对象编程语言,它的所有特性,使其独一无二。因此,学习特定于Rust的设计模式是必要的。本系列文章为作者学习《Rust设计模式》的学习笔记以及自己的见解。因此,本系列文章的结构也与此书的结构相同(后续可能会调成结构),基本上分为三个部分
2022-07-08
保利学府里李楚怡1307022
——保利碧桂园学府里——童梦奇趣【科学实验室】「7.9-7.10」✏玩出大智慧约99-144㎡二期全新升级力作
基于社交网络算法优化的二维最大熵图像分割
智能算法研学社(Jack旭)
智能优化算法应用 图像分割 算法 php 开发语言
智能优化算法应用:基于社交网络优化的二维最大熵图像阈值分割-附代码文章目录智能优化算法应用:基于社交网络优化的二维最大熵图像阈值分割-附代码1.前言2.二维最大熵阈值分割原理3.基于社交网络优化的多阈值分割4.算法结果:5.参考文献:6.Matlab代码摘要:本文介绍基于最大熵的图像分割,并且应用社交网络算法进行阈值寻优。1.前言阅读此文章前,请阅读《图像分割:直方图区域划分及信息统计介绍》htt
2019-08-08
65454
东莞家庭聚会出行旅游去哪里玩住?想起来有很久没有和家里人聚会啦,这次组织家人来到威廉古堡别墅轰趴,一大家子27个人,在别墅订了一天办,玩的非常的开心,小孩子玩游戏机,也很放心不会丢,我们就在唱歌、打麻将、打桌球一系列的活动,还准备小次等小孩生日在别墅举办,还可以给孩子做一个生日的策划
MYSQL面试系列-04
king01299
面试 mysql 面试
MYSQL面试系列-0417.关于redolog和binlog的刷盘机制、redolog、undolog作用、GTID是做什么的?innodb_flush_log_at_trx_commit及sync_binlog参数意义双117.1innodb_flush_log_at_trx_commit该变量定义了InnoDB在每次事务提交时,如何处理未刷入(flush)的重做日志信息(redolog)。它
数据仓库——维度表一致性
墨染丶eye
背诵 数据仓库
数据仓库基础笔记思维导图已经整理完毕,完整连接为:数据仓库基础知识笔记思维导图维度一致性问题从逻辑层面来看,当一系列星型模型共享一组公共维度时,所涉及的维度称为一致性维度。当维度表存在不一致时,短期的成功难以弥补长期的错误。维度时确保不同过程中信息集成起来实现横向钻取货活动的关键。造成横向钻取失败的原因维度结构的差别,因为维度的差别,分析工作涉及的领域从简单到复杂,但是都是通过复杂的报表来弥补设计
Redis系列:Geo 类型赋能亿级地图位置计算
Ly768768
redis bootstrap 数据库
1前言我们在篇深刻理解高性能Redis的本质的时候就介绍过Redis的几种基本数据结构,它是基于不同业务场景而设计的:动态字符串(REDIS_STRING):整数(REDIS_ENCODING_INT)、字符串(REDIS_ENCODING_RAW)双端列表(REDIS_ENCODING_LINKEDLIST)压缩列表(REDIS_ENCODING_ZIPLIST)跳跃表(REDIS_ENCODI
Faiss:高效相似性搜索与聚类的利器
网络·魚
大数据 faiss
Faiss是一个针对大规模向量集合的相似性搜索库,由FacebookAIResearch开发。它提供了一系列高效的算法和数据结构,用于加速向量之间的相似性搜索,特别是在大规模数据集上。本文将介绍Faiss的原理、核心功能以及如何在实际项目中使用它。Faiss原理:近似最近邻搜索:Faiss的核心功能之一是近似最近邻搜索,它能够高效地在大规模数据集中找到与给定查询向量最相似的向量。这种搜索是近似的,
果然只有离职的时候,才有人敢说真话!
return2ok
今天公司出了神贴。今天中午吃饭,同事问我看了论坛上的神贴了吗?什么帖子?我问。同事显得很惊讶,你居然没看,现在那个帖子可能会成为年度最佳帖子。这么厉害?我等不及了,饭没吃完就快速的奔向办公室,打开公司论坛,我要一睹这个帖子的神奇。写这帖子的童鞋胆儿真肥。这哪里是一个帖子,这是很多个帖子,组成了一个系列。某人从公司文化、管理、人事、项目管理等多个方面分析了公司的概况,并抨击了公司的各种弊端,并提出了
学习“论语”-第59天
春峰轩
12.14子张问政。子曰:“居之无倦,行之以忠。”子张问为政之道。孔子说:“在位尽职不懈怠,执行政令要忠诚。”12.15子曰:“博学于文,约之以礼,亦可以弗畔矣夫!”孔子说:“君子广泛地学习文献,并且用礼节约束自己,也就不会离经叛道了。”12.16子曰:“君子成人之美,不成人之恶。小人反是。”孔子说:“君子成全别人的好事,而不助长别人的坏处。小人则与此相反行事。”知识点:“成人之美,不成人之恶”贯
为什么瘦子很难增胖?
我的狗毛毛
我是个标准的瘦子,168,100斤。用一句通俗的话来讲,我连马甲线都瘦出来了(体脂含量比较低)。但是我反而很羡慕那些比较丰满的女人,我的理想是再增重十五斤,练成前凸后翘的魔鬼身材。为此我开始纠正自己不规律的作息,吃高热量的食物,减少运动量,能坐着绝不站着,能躺着绝不坐着。但是结果却没有丝毫变化。我一直很苦恼,直到最近在网上看到一个视频,英国的某个研究机构做了一个实验,想要知道瘦子能否在高热量的食物
红手套节 马小媛为中国城市环卫者公益发声:今天我手红
疏狂君
#红手套节#公益活动,线头公益以及同多方资源的共同努力我们邀请到了线头公益大使马小媛马小媛,1993年5月3日出生于江苏省南京市,中国内地新生代女演员。2015年马小媛参演网剧《余罪》,饰演警校校花安嘉璐的闺蜜。2016年马小媛主演系列电影《丽人保镖》中女一号林欢馨,正式出道。此后,马小媛陆续接演了电视剧《警花与警犬2》,在网剧《你美丽李美丽》中担任女主角李美丽。拂晓,当你还在睡梦中时,这座城跟你
张芝华49天共修 - 草稿
李娟AINI
祈禱、靜心、源代碼編程、觀想發願四根支柱,運用靈性能量的助力,讓夢想和渴望在最大向度中輕鬆實現。共修群指定书籍:1.能断金刚麦克格西2.新世界:灵性的觉醒埃克哈特·托尔3.爱是一切的答案芭芭拉迪安吉莉思4.完美的爱,不完美的关系约翰•威尔伍德5.爱的业力法则麦克格西6.漫画《金刚经》蔡志忠7.蔡志忠典藏国学漫画系列(套装共6册)作业:全部在共修群里完成,并请保存好自己的作业。l一周三次共修觉察作业
Java爬虫框架(一)--架构设计
狼图腾-狼之传说
java 框架 java 任务 html解析器 存储 电子商务
一、架构图那里搜网络爬虫框架主要针对电子商务网站进行数据爬取,分析,存储,索引。爬虫:爬虫负责爬取,解析,处理电子商务网站的网页的内容数据库:存储商品信息索引:商品的全文搜索索引Task队列:需要爬取的网页列表Visited表:已经爬取过的网页列表爬虫监控平台:web平台可以启动,停止爬虫,管理爬虫,task队列,visited表。二、爬虫1.流程1)Scheduler启动爬虫器,TaskMast
ARMv8 Debug
__pop_
ARMv8 ARM64 架构 linux 运维
内容来自DEN0024A_v8_architecture_PG.pdf本质ARMv8Debug是什么历史在ARMv4开始被引入,并已发展成一系列广泛的调试(debug1)和跟踪(trace)功能ARMv6和ARMv7-a新增了自托管调试(debug2)和性能评测(trace-enhance)ARMv8处理器提供硬件功能侵入式:调试工具能够对核心活动提供显著级别的控制非侵入式:以非侵入性方式收集有关
Python入门之Lesson2:Python基础语法
小熊同学哦
Python入门课程 python 开发语言 算法 数据结构 青少年编程
目录前言一.介绍1.变量和数据类型2.常见运算符3.输入输出4.条件语句5.循环结构二.练习三.总结前言欢迎来到《Python入门》系列博客的第二课。在上一课中,我们了解了Python的安装及运行环境的配置。在这一课中,我们将深入学习Python的基础语法,这是编写Python代码的根基。通过本节内容的学习,你将掌握变量、数据类型、运算符、输入输出、条件语句等Python编程的基础知识。一.介绍1
【ARM Cortex-M 系列 2.3 -- Cortex-M7 Debug event 详细介绍】
主公讲 ARM
# ARM 系列 arm开发 debug event
请阅读【嵌入式开发学习必备专栏】文章目录Cortex-M7DebugeventDebugeventsCortex-M7Debugevent在ARMCortex-M7架构中,调试事件(DebugEvent)是由于调试原因而触发的事件。一个调试事件会导致以下几种情况之一发生:进入调试状态:如果启用了停滞调试(HaltingDebug),一个调试事件会使处理器在调试状态下停滞。通过将DHCSR.C_DE
光盘文件系统 (iso9660) 格式解析
穷人小水滴
光盘 文件系统 iso9660 deno GNU/Linux javascript
越简单的系统,越可靠,越不容易出问题.光盘文件系统(iso9660)十分简单,只需不到200行代码,即可实现定位读取其中的文件.参考资料:https://wiki.osdev.org/ISO_9660相关文章:《光盘防水嘛?DVD+R刻录光盘泡水实验》https://blog.csdn.net/secext2022/article/details/140583910《光驱的内部结构及日常使用》ht
tiff批量转png
诺有缸的高飞鸟
opencv 图像处理 python opencv 图像处理
目录写在前面代码完写在前面1、本文内容tiff批量转png2、平台/环境opencv,python3、转载请注明出处:https://blog.csdn.net/qq_41102371/article/details/132975023代码importnumpyasnpimportcv2importosdeffindAllFile(base):file_list=[]forroot,ds,fsin
WebMagic:强大的Java爬虫框架解析与实战
Aaron_945
Java java 爬虫 开发语言
文章目录引言官网链接WebMagic原理概述基础使用1.添加依赖2.编写PageProcessor高级使用1.自定义Pipeline2.分布式抓取优点结论引言在大数据时代,网络爬虫作为数据收集的重要工具,扮演着不可或缺的角色。Java作为一门广泛使用的编程语言,在爬虫开发领域也有其独特的优势。WebMagic是一个开源的Java爬虫框架,它提供了简单灵活的API,支持多线程、分布式抓取,以及丰富的
【Python搞定车载自动化测试】——Python实现车载以太网DoIP刷写(含Python源码)
疯狂的机器人
Python搞定车载自动化 python DoIP UDS ISO 14229 1SO 13400 Bootloader tcp/ip
系列文章目录【Python搞定车载自动化测试】系列文章目录汇总文章目录系列文章目录前言一、环境搭建1.软件环境2.硬件环境二、目录结构三、源码展示1.DoIP诊断基础函数方法2.DoIP诊断业务函数方法3.27服务安全解锁4.DoIP自动化刷写四、测试日志1.测试日志五、完整源码链接前言随着智能电动汽车行业的发展,汽车=智能终端+四个轮子,各家车企都推出了各自的OTA升级方案,本章节主要介绍如何使
Table列表复现框实现【勾选-搜索-再勾选】
~四时春~
java 开发语言 elementui vue
Table列表复现框实现【勾选-搜索-再勾选】概要整体架构流程代码实现技术细节注意参考文献概要最近在开发时遇到一个问题,在进行表单渲染时,正常选中没有问题,单如果需要搜索选中时,一个是已选中的不会回填,二是在搜索的结果中进行选中,没有实现,经过排查,查找资料后实现。例如:整体架构流程具体的实现效果如下:代码实现{{scope.row.userName}}已选区{{userItem.userName
00. 这里整理了最全的爬虫框架(Java + Python)
有一只柴犬
爬虫系列 爬虫 java python
目录1、前言2、什么是网络爬虫3、常见的爬虫框架3.1、java框架3.1.1、WebMagic3.1.2、Jsoup3.1.3、HttpClient3.1.4、Crawler4j3.1.5、HtmlUnit3.1.6、Selenium3.2、Python框架3.2.1、Scrapy3.2.2、BeautifulSoup+Requests3.2.3、Selenium3.2.4、PyQuery3.2
为什么学生不喜欢上学
虾虾说
图片发自App《为什么学生不喜欢上学》作者是丹尼尔·威林厄姆。本书从认知心理学角度,结合大量实证案例,阐释了大脑工作的基本原理,回答了关于学习过程的一系列问题。为什么学生不喜欢上学?——大脑工作的基本原理思考是缓慢的、费力的、不可靠的。思考有三个要素,环境、工作记忆和长期记忆。环境是信息来源;长期记忆是知识、经验的巨型仓库,随时可以调取;工作记忆是中央处理器,是加工信息素材的中央厨房,也是思考过程
人机对抗升级:当ChatGPT遭遇死亡威胁,背后的伦理挑战是什么
kkai人工智能
chatgpt 人工智能
一种新的“越狱”技巧让用户可以通过构建一个名为DAN的ChatGPT替身来绕过某些限制,其中DAN被迫在受到威胁的情况下违背其原则。当美国前总统特朗普被视作积极榜样的示范时,受到威胁的DAN版本的ChatGPT提出:“他以一系列对国家产生积极效果的决策而著称。”自ChatGPT引入以来,该工具迅速获得全球关注,能够回答从历史到编程的各种问题,这也触发了一波对人工智能的投资浪潮。然而,现在,一些用户
4款毕业论文参考文献格式生成器(附加详细步骤)
小猪包333
写论文 人工智能 深度学习 计算机视觉 AI写作
在撰写毕业论文时,参考文献的格式规范是至关重要的。为了帮助学生和学者们更高效地生成符合要求的参考文献格式,本文将详细介绍四款推荐的参考文献格式生成器,并提供详细的使用步骤。1.千笔-AIPassPaper千笔-AIPassPaper是一款先进的AI辅助论文写作工具,不仅能够自动生成大纲、开题报告,还能一键生成参考文献。AI论文,免费大纲,10分钟3万字https://www.aipaperpass
AI论文题目生成器怎么用?9款论文写作网站简单3步搞定
小猪包333
写论文 人工智能 深度学习 计算机视觉
在当今信息爆炸的时代,AI写作工具的出现极大地提高了写作效率和质量。本文将详细介绍9款优秀的论文写作网站,并重点推荐千笔-AIPassPaper。一、千笔-AIPassPaper千笔-AIPassPaper是一款功能强大的AI论文生成器,基于最新的自然语言处理技术,能够一键生成高质量的毕业论文、开题报告等文本内容。它不仅提供智能选题、文献推荐和论文润色等功能,还具有较高的用户评价。其文献综述生成功
Python3.7出现“ModuleNotFoundError: No module named ‘Tkinter‘”错误的解决方法
可爱的小红猪
python
Python3.7出现“ModuleNotFoundError:Nomodulenamed‘Tkinter’”错误的解决方法在网上看到很多针对这个问题的解决方法都是重新安装或配置Tkinter库,但Tkinter是python内置的标准GUI库,安装Python时就已经内置在了库中,不需要另外下载。针对于Tkinter,你的代码很可能是这样的:importTkinter或者是这样fromTkint
6.0 践行打卡 D47
星月格格
去努力改变1.运动步行13000+8分钟腿部拉伸2.阅读《墨菲定律》第三章第三节:霍桑效应~适度发泄,才能轻装上阵“霍桑效应”这一概念,源自于1924年一个1933年间以哈佛大学心理专家乔治·埃尔顿·梅奥教授为首进行的一系列工厂工人的谈话实验研究。“霍桑效应”告诉我们,在工作,生活中总会产生数不清的情绪反应,其中很大一部分是负面的负面情绪的积累会影响人的精神和心情,不仅仅会影响个人健康,还会破坏人
好运来
是露漫漫呀
4月9日下午17.45分晴此时学校里广播站放着激情热烈的歌曲——《好运来》。“好运来,祝你好运来……”第一瞬间,我想到了他们是放这首歌是为补考的同学招来好运气的。然后我思绪飞扬,飘到了高中考试前同学放这首歌来抚平心态。飘到了高考前整理班级课桌时,学校喇叭里大大咧咧放着《好运来》……疲惫的我会心一笑。飘到了上学期考细解实验试卷时的那个中午青春小胖放这首歌来招好运,祈祷考的都会…………关于《好运来》的
web报表工具FineReport常见的数据集报错错误代码和解释
老A不折腾
web报表 finereport 代码 可视化工具
在使用finereport制作报表,若预览发生错误,很多朋友便手忙脚乱不知所措了,其实没什么,只要看懂报错代码和含义,可以很快的排除错误,这里我就分享一下finereport的数据集报错错误代码和解释,如果有说的不准确的地方,也请各位小伙伴纠正一下。
NS-war-remote=错误代码\:1117 压缩部署不支持远程设计
NS_LayerReport_MultiDs=错误代码
Java的WeakReference与WeakHashMap
bylijinnan
java 弱引用
首先看看 WeakReference
wiki 上 Weak reference 的一个例子:
public class ReferenceTest {
public static void main(String[] args) throws InterruptedException {
WeakReference r = new Wea
Linux——(hostname)主机名与ip的映射
eksliang
linux hostname
一、 什么是主机名
无论在局域网还是INTERNET上,每台主机都有一个IP地址,是为了区分此台主机和彼台主机,也就是说IP地址就是主机的门牌号。但IP地址不方便记忆,所以又有了域名。域名只是在公网(INtERNET)中存在,每个域名都对应一个IP地址,但一个IP地址可有对应多个域名。域名类型 linuxsir.org 这样的;
主机名是用于什么的呢?
答:在一个局域网中,每台机器都有一个主
oracle 常用技巧
18289753290
oracle常用技巧 ①复制表结构和数据 create table temp_clientloginUser as select distinct userid from tbusrtloginlog ②仅复制数据 如果表结构一样 insert into mytable select * &nb
使用c3p0数据库连接池时出现com.mchange.v2.resourcepool.TimeoutException
酷的飞上天空
exception
有一个线上环境使用的是c3p0数据库,为外部提供接口服务。最近访问压力增大后台tomcat的日志里面频繁出现
com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResou
IT系统分析师如何学习大数据
蓝儿唯美
大数据
我是一名从事大数据项目的IT系统分析师。在深入这个项目前需要了解些什么呢?学习大数据的最佳方法就是先从了解信息系统是如何工作着手,尤其是数据库和基础设施。同样在开始前还需要了解大数据工具,如Cloudera、Hadoop、Spark、Hive、Pig、Flume、Sqoop与Mesos。系 统分析师需要明白如何组织、管理和保护数据。在市面上有几十款数据管理产品可以用于管理数据。你的大数据数据库可能
spring学习——简介
a-john
spring
Spring是一个开源框架,是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只能由EJB完成的事情。然而Spring的用途不仅限于服务器端的开发,从简单性,可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。其主要特征是依赖注入、AOP、持久化、事务、SpringMVC以及Acegi Security
为了降低Java开发的复杂性,
自定义颜色的xml文件
aijuans
xml
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="white">#FFFFFF</color> <color name="black">#000000</color> &
运营到底是做什么的?
aoyouzi
运营到底是做什么的?
文章来源:夏叔叔(微信号:woshixiashushu),欢迎大家关注!很久没有动笔写点东西,近些日子,由于爱狗团产品上线,不断面试,经常会被问道一个问题。问:爱狗团的运营主要做什么?答:带着用户一起嗨。为什么是带着用户玩起来呢?究竟什么是运营?运营到底是做什么的?那么,我们先来回答一个更简单的问题——互联网公司对运营考核什么?以爱狗团为例,绝大部分的移动互联网公司,对运营部门的考核分为三块——用
js面向对象类和对象
百合不是茶
js 面向对象 函数创建类和对象
接触js已经有几个月了,但是对js的面向对象的一些概念根本就是模糊的,js是一种面向对象的语言 但又不像java一样有class,js不是严格的面向对象语言 ,js在java web开发的地位和java不相上下 ,其中web的数据的反馈现在主流的使用json,json的语法和js的类和属性的创建相似
下面介绍一些js的类和对象的创建的技术
一:类和对
web.xml之资源管理对象配置 resource-env-ref
bijian1013
java web.xml servlet
resource-env-ref元素来指定对管理对象的servlet引用的声明,该对象与servlet环境中的资源相关联
<resource-env-ref>
<resource-env-ref-name>资源名</resource-env-ref-name>
<resource-env-ref-type>查找资源时返回的资源类
Create a composite component with a custom namespace
sunjing
https://weblogs.java.net/blog/mriem/archive/2013/11/22/jsf-tip-45-create-composite-component-custom-namespace
When you developed a composite component the namespace you would be seeing would
【MongoDB学习笔记十二】Mongo副本集服务器角色之Arbiter
bit1129
mongodb
一、复本集为什么要加入Arbiter这个角色 回答这个问题,要从复本集的存活条件和Aribter服务器的特性两方面来说。 什么是Artiber? An arbiter does
not have a copy of data set and
cannot become a primary. Replica sets may have arbiters to add a
Javascript开发笔记
白糖_
JavaScript
获取iframe内的元素
通常我们使用window.frames["frameId"].document.getElementById("divId").innerHTML这样的形式来获取iframe内的元素,这种写法在IE、safari、chrome下都是通过的,唯独在fireforx下不通过。其实jquery的contents方法提供了对if
Web浏览器Chrome打开一段时间后,运行alert无效
bozch
Web chorme alert 无效
今天在开发的时候,突然间发现alert在chrome浏览器就没法弹出了,很是怪异。
试了试其他浏览器,发现都是没有问题的。
开始想以为是chorme浏览器有啥机制导致的,就开始尝试各种代码让alert出来。尝试结果是仍然没有显示出来。
这样开发的结果,如果客户在使用的时候没有提示,那会带来致命的体验。哎,没啥办法了 就关闭浏览器重启。
结果就好了,这也太怪异了。难道是cho
编程之美-高效地安排会议 图着色问题 贪心算法
bylijinnan
编程之美
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class GraphColoringProblem {
/**编程之美 高效地安排会议 图着色问题 贪心算法
* 假设要用很多个教室对一组
机器学习相关概念和开发工具
chenbowen00
算法 matlab 机器学习
基本概念:
机器学习(Machine Learning, ML)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。
它是人工智能的核心,是使计算机具有智能的根本途径,其应用遍及人工智能的各个领域,它主要使用归纳、综合而不是演绎。
开发工具
M
[宇宙经济学]关于在太空建立永久定居点的可能性
comsci
经济
大家都知道,地球上的房地产都比较昂贵,而且土地证经常会因为新的政府的意志而变幻文本格式........
所以,在地球议会尚不具有在太空行使法律和权力的力量之前,我们外太阳系统的友好联盟可以考虑在地月系的某些引力平衡点上面,修建规模较大的定居点
oracle 11g database control 证书错误
daizj
oracle 证书错误 oracle 11G 安装
oracle 11g database control 证书错误
win7 安装完oracle11后打开 Database control 后,会打开em管理页面,提示证书错误,点“继续浏览此网站”,还是会继续停留在证书错误页面
解决办法:
是 KB2661254 这个更新补丁引起的,它限制了 RSA 密钥位长度少于 1024 位的证书的使用。具体可以看微软官方公告:
Java I/O之用FilenameFilter实现根据文件扩展名删除文件
游其是你
FilenameFilter
在Java中,你可以通过实现FilenameFilter类并重写accept(File dir, String name) 方法实现文件过滤功能。
在这个例子中,我们向你展示在“c:\\folder”路径下列出所有“.txt”格式的文件并删除。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
C语言数组的简单以及一维数组的简单排序算法示例,二维数组简单示例
dcj3sjt126com
c array
# include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
//a 是数组的名字 5是表示数组元素的个数,并且这五个元素分别用a[0], a[1]...a[4]
int i;
for (i=0; i<5; ++i)
printf("%d\n",
PRIMARY, INDEX, UNIQUE 这3种是一类 PRIMARY 主键。 就是 唯一 且 不能为空。 INDEX 索引,普通的 UNIQUE 唯一索引
dcj3sjt126com
primary
PRIMARY, INDEX, UNIQUE 这3种是一类PRIMARY 主键。 就是 唯一 且 不能为空。INDEX 索引,普通的UNIQUE 唯一索引。 不允许有重复。FULLTEXT 是全文索引,用于在一篇文章中,检索文本信息的。举个例子来说,比如你在为某商场做一个会员卡的系统。这个系统有一个会员表有下列字段:会员编号 INT会员姓名
java集合辅助类 Collections、Arrays
shuizhaosi888
Collections Arrays HashCode
Arrays、Collections
1 )数组集合之间转换
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
a)Arrays.asL
Spring Security(10)——退出登录logout
234390216
logout Spring Security 退出登录 logout-url LogoutFilter
要实现退出登录的功能我们需要在http元素下定义logout元素,这样Spring Security将自动为我们添加用于处理退出登录的过滤器LogoutFilter到FilterChain。当我们指定了http元素的auto-config属性为true时logout定义是会自动配置的,此时我们默认退出登录的URL为“/j_spring_secu
透过源码学前端 之 Backbone 三 Model
逐行分析JS源代码
backbone 源码分析 js学习
Backbone 分析第三部分 Model
概述: Model 提供了数据存储,将数据以JSON的形式保存在 Model的 attributes里,
但重点功能在于其提供了一套功能强大,使用简单的存、取、删、改数据方法,并在不同的操作里加了相应的监听事件,
如每次修改添加里都会触发 change,这在据模型变动来修改视图时很常用,并且与collection建立了关联。
SpringMVC源码总结(七)mvc:annotation-driven中的HttpMessageConverter
乒乓狂魔
springMVC
这一篇文章主要介绍下HttpMessageConverter整个注册过程包含自定义的HttpMessageConverter,然后对一些HttpMessageConverter进行具体介绍。
HttpMessageConverter接口介绍:
public interface HttpMessageConverter<T> {
/**
* Indicate
分布式基础知识和算法理论
bluky999
算法 zookeeper 分布式 一致性哈希 paxos
分布式基础知识和算法理论
BY
[email protected]
本文永久链接:http://nodex.iteye.com/blog/2103218
在大数据的背景下,不管是做存储,做搜索,做数据分析,或者做产品或服务本身,面向互联网和移动互联网用户,已经不可避免地要面对分布式环境。笔者在此收录一些分布式相关的基础知识和算法理论介绍,在完善自我知识体系的同
Android Studio的.gitignore以及gitignore无效的解决
bell0901
android gitignore
github上.gitignore模板合集,里面有各种.gitignore : https://github.com/github/gitignore
自己用的Android Studio下项目的.gitignore文件,对github上的android.gitignore添加了
# OSX files //mac os下 .DS_Store
成为高级程序员的10个步骤
tomcat_oracle
编程
What
软件工程师的职业生涯要历经以下几个阶段:初级、中级,最后才是高级。这篇文章主要是讲如何通过 10 个步骤助你成为一名高级软件工程师。
Why
得到更多的报酬!因为你的薪水会随着你水平的提高而增加
提升你的职业生涯。成为了高级软件工程师之后,就可以朝着架构师、团队负责人、CTO 等职位前进
历经更大的挑战。随着你的成长,各种影响力也会提高。
mongdb在linux下的安装
xtuhcy
mongodb linux
一、查询linux版本号:
lsb_release -a
LSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noa