]+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,文献爬取,手把手系列)
关于市场主流大模型的系统性整理和分析(必看系列,附汇总表格)
GA琥珀
LLM 人工智能 语言模型
一、旗舰专有模型生态系统在生成式AI的高端市场,几家公司凭借其强大的研发实力和资本支持,构建了以旗舰专有模型为核心的生态系统。它们通过API和订阅服务提供最先进的功能,引领着技术发展的方向。1.1OpenAI:在位的创新者OpenAI作为行业的先行者,其战略核心是建立一道“性能护城河”。通过持续发布性能领先(且价格高昂)的模型,锁定那些愿意为顶级能力支付溢价的用户和企业。其快速的迭代周期旨在使其始
【Vue CLI】手把手教你撸插件
vivo互联网技术
本文首发于vivo互联网技术微信公众号链接:https://mp.weixin.qq.com/s/Rl8XLUX7isjXNUmbw0-wow作者:ZhuPing现如今Vue作为主流的前端框架之一,其健全的配套工具,活跃的开源社区,让广发码农热衷追捧。VueCLI作为其官方的开发构建工具,目前已更新迭代到4.x版本,其内部集成了日常开发用到的打包压缩等功能,简化了常规自己动手配置webpack的烦
【Linux基础知识系列】第五十一篇 - Linux文件命名规范与格式
望获linux
Linux基础知识系列 java 服务器 linux 开发语言 前端 数据库 嵌入式软件
在Linux系统中,文件命名规范和格式对于文件的组织和管理至关重要。合理的文件命名不仅可以帮助用户快速识别文件的内容和用途,还能避免文件名冲突和错误。掌握Linux文件命名规范和常见格式,对于开发者和系统管理员来说是非常重要的技能。本文将详细介绍Linux系统中文件命名的规范和常见格式,包括命名约定和文件扩展名的意义,帮助读者合理管理文件。核心概念1.文件名文件名是文件的标识符,用于在文件系统中唯
孩子们,我想对你们说
恩惠有嘉
三年前,你们怀着对母校的敬仰,对知识的渴望和对未来的美好憧憬,来到了这里。三年光阴,1000多个日日夜夜,学校每个角落都留下了你们的足迹∶教室里有你们奋笔疾书的身影,慷慨激昂的言语;球场上有你们奋勇拼搏,勇争第一的身姿;实验室里内,你们孜孜不倦的探求真知。为了中考,你们度过了多少个寒风凛冽的清晨,多少个挥汗如雨的午后,多少个孤灯独明的长夜……成就总是和努力成正比,你们承受了风吹雨打,品尝了酸甜苦辣
centos7安装python3并配置环境变量
weixin_46119222
centos python3.11
在CentOS7上安装Python3并将其设置为默认版本,可以按照以下步骤进行:1.安装Python3首先,你需要安装Python3。在CentOS7上,你可以通过yum包管理器来安装Python3。执行以下命令:bash复制代码sudoyuminstallpython3这个命令会使用yum来安装Python3。2.安装依赖文件(可选)如果你打算从源代码安装Python3,或者需要某些特定的库和功
性能优化实践:Modbus 在高并发场景下的吞吐量提升(一)
一、引言**在工业自动化领域,Modbus协议凭借其简单易用、开放性高以及广泛的设备支持等特点,成为了设备间通信的重要标准之一。自1979年由Modicon公司(现为施耐德电气)首次开发以来,Modbus协议历经多年发展,已广泛应用于各类工业场景,从工厂自动化生产线到智能建筑控制系统,从能源管理系统到远程监控平台,Modbus协议无处不在,连接着各种不同类型的设备,实现数据的交换与控制指令的传递。
PHP手册之clone
killtl
前言PHP手册系列文章,会挑选一些手册中有意思的评论进行翻译手册目录:语言参考---类与对象---clone参考详情评论当类内部调用__clone对属性进行同类实例clone时,会造成循环clone,但是实际代码不会这样任由你胡作非为,但是经过本人测试,会造成clone循环调用classFoo{var$that;function__clone(){$this->that=clone$this->t
人工智能服务器处理器的全新定义 两大头部品牌旗舰款的王者之争!云储存cpu_云服务器处理器_企业服务器处理器
一、旗舰处理器架构解析IntelXeon6900系列代表着英特尔在服务器处理器领域的最新成果,采用增强版Intel7制程工艺打造。该系列最高配置56个物理核心,通过超线程技术支持112个逻辑线程,在处理多线程任务时展现出卓越的性能表现。内存子系统方面,支持8通道DDR5-4800内存配置,最高可扩展至4TB容量,为内存密集型应用提供了充足带宽。特别值得一提的是其集成的AMX高级矩阵扩展指令集,这项
python--自动化的机器学习(AutoML)
Q_ytsup5681
python 自动化 机器学习
自动化机器学习(AutoML)是一种将自动化技术应用于机器学习模型开发流程的方法,旨在简化或去除需要专业知识的复杂步骤,让非专家用户也能轻松创建和部署机器学习模型**[^3^]。具体介绍如下:1.自动化的概念:自动化是指使设备在无人或少量人参与的情况下完成一系列任务的过程。这一概念随着电子计算机的发明和发展而不断进化,从最初的物理机械到后来的数字程序控制,再到现在的人工智能和机器学习,自动化已经渗
文献笔记八十一:植物长链非编码RNA数据库PLncDB 2.0
小明的数据分析笔记本
论文链接https://academic.oup.com/nar/article/49/D1/D1489/5932847本地文件gkaa910.pdf
小架构step系列14:白盒集成测试原理
秋千码途
集成测试 java 架构
1概述这里的白盒测试是指开发编写测试代码来进行测试,集成测试是指从Controller开始对http接口调用的整个流程进行测试。这个流程就是对一个http请求的响应流程,正常运行的时候是通过springboot内嵌的tomcat来启动一个webserver来监听http请求,然后响应该http请求。在测试的时候,如果也需要启动一个webserver来监听请求,那么测试就更加困难了一些。还好spri
Redis面试精讲 Day 3:Redis持久化机制详解
在未来等你
Redis面试专栏 Redis 面试题 持久化 RDB AOF 数据库 缓存
【Redis面试精讲Day3】Redis持久化机制详解文章标签Redis,面试题,持久化,RDB,AOF,数据库,缓存,后端开发,分布式系统文章简述本文是"Redis面试精讲"系列第3天内容,深入解析Redis持久化机制这一面试高频考点。文章从基础概念出发,详细剖析RDB和AOF两种持久化方式的实现原理、触发机制和优缺点对比,提供多语言客户端操作示例和性能测试数据。针对"如何选择持久化策略"、"A
中原焦点团队网络初级第30期 杨明霞分享第40天
54dea169dd4d
在咨询过程中,SFBT咨询师于当事人乃是一个治疗的团队,相互合作并一起进行实验。咨询师只是通过顾问角色及合作伙伴的关系,来协助当事人达成所欲的目标。SFBT咨询师对当事人怀有真诚好奇的心,尊重接纳当事人的各种知觉,视当事人为一独立完整的个体,鼓励当事人重视与信任自己体验生活的知觉与方式。SFBT咨询师是邀请的专家,是一个协助当事人辨识自己目标、优势与所欲改变方向的专家,是一个创造改变脉络,却不主导
STL 简介(标准模板库)
前言通过对C++的特性,类和对象的学习和C++的内存管理对C++基本上有了全面的认识,但是C++的核心在于STL一、STL简介什么是STLC++STL(StandardTemplateLibrary,标准模板库)是C++编程语言中一个功能强大的模板库,它提供了一系列通用的数据结构和算法。STL的设计基于泛型编程,这意味着它使用模板来编写独立于任何特定数据类型的代码。STL的核心组件包括容器(如向量
张柏芝与陈乔恩同框,两人竟只相差1岁?
守护你的喵
张柏芝和陈乔恩罕见同框!两个女神貌美如花。张柏芝身穿秋冬系列皮衣,内搭及膝吊带连身裙,整个人显得既高贵大气又性感。陈乔恩一袭褐色的v领修身针织衫,搭配红唇,肤白美貌。两位女神一同出镜,难免让网友们互相比较了起来,经过一番比较,不难看出的是,陈乔恩38岁,张柏芝37岁,明明只相差了一岁左右的两人,陈乔恩要显得年轻得多,张柏芝却比陈乔恩老了很多。女神同框不得不说陈乔恩30多岁保养的很好,皮肤白皙水嫩,
为什么说永远也不要考验人性
孤_b0d8
很多著名的实验都告诉我们一个道理,人性经受不住考验。在《ThePush》这档真人秀中,达伦布朗选择了四名没有犯罪前科,没有情绪问题的正常人当小白鼠,作为试验对象,看能不能在72小时把他们变成一个杀人犯。结局很让人震惊,四个实验对象,三个人都成为了“杀人犯,”仅仅只是72个小时,就能让一个正常人变成杀人犯,让人不寒而栗。在实验开始的时候,通过一个小小的测试,达伦布朗选择了4名顺从度高的人,并告知他们
frida objection注入时frida.core.RPCException: ReferenceError: ‘ObjC‘ is not defined解决
马戏团小丑
java android
最新的17.0.xx版本frida进行objection注入时会报错PSC:\Users\19583>objection-gcom.example.hellojniexploreC:\Users\19583\AppData\Local\Programs\Python\Python312\Lib\site-packages\objection\utils\update_checker.py:7:Us
SQLite数据库文件损坏的可能几种情况(一)
界忆
人工智能 数据库 SQLite 数据库 sqlite c++
返回:SQLite—系列文章目录上一篇:SQLiteC/C++接口详细介绍sqlite3_stmt类(十三)下一篇:SQLite使用的临时文件(二)概述SQLite数据库具有很强的抗损坏能力。如果应用程序崩溃,或操作系统崩溃,甚至电源故障发生在事务中间,部分写入的事务应在下次自动回滚将访问数据库文件。恢复过程已完全完成自动,不需要用户采取任何行动或应用程序。尽管SQLite可以抵抗数据库损坏,但它
Linux检测远程端口是否打开的六种方法
岚天start
运维 网络 linux ssh 网络
Linux检测远程端口是否打开的六种方法服务器环境:[root@java-test-server~]#cat/etc/redhat-releaseCentOSLinuxrelease7.6.1810(Core)方法一:Telnet命令【yum-yinstalltelnet】#端口通显示如下(实验IP为自定义IP)[root@java-test-server~]#telnet88.88.88.888
大一暑假适合学51单片机吗?
淘晶驰AK
51单片机 嵌入式硬件 单片机
大一暑假学51单片机,简直是老天爷赏饭吃的黄金窗口。我当时就是靠着这两个月,把从课本上看来的C语言指针、循环语句,变成了能让LED按节奏跳舞的真本事。学期里总被高数作业和英语背单词挤得没整块时间,焊个电路板还得算着实验室关门时间。暑假就不一样了,早上自然醒后泡杯咖啡,搬个小桌子到阳台,开发板一铺就是一整天。记得第一次烧写程序时,手抖着插杜邦线,结果把VCC接到了GND,开发板瞬间冒出股焦味——后来
快讯,2021印尼羽毛球大师赛,桃田贤斗再次高歌猛进
虫大话体坛
快讯,2021印尼羽毛球大师赛,桃田贤斗再次高歌猛进.。北京时间11月16日,2021印尼羽毛球大师赛(超级750系列)开始首轮比赛的争夺,在刚刚结束的一场男单焦点赛事中,桃田贤斗以2比0战胜小苏吉亚托,再奏凯歌。桃田贤斗自东京奥运会后基本没有休息过,一直在海外征战。丹麦赛和安赛龙的对战成为经典,直到最后一局才分出胜负。桃田贤斗在东京奥运会前曾遭受过车祸和感染新冠,但凭借惊人的毅力,状态逐渐恢复。
5月阅读写作践行总结
旦卉
图片发自App01阅读《臣服实验》因是台版书,繁体竖排,有点挑战大脑的惯性。刚开始时读慢一点,几天下来就适应了。很是敬佩这种在困境中,不内耗、不抱怨,却全然地努力,臣服于生命之流的状态。《走出剧情——活在人生的真相里》看过武志红老师的书籍后,再来看这本,才理解到武老师的书荐:“能把潜意识中复杂缠绕的感受和动力,如此清澈透亮地表达出来,这样的文字总是让我感动和赞叹。”尹建莉的书荐:“我怀疑,李雪24
AI 人工智能与 Copilot 碰撞出的火花
AI天才研究院
AI大模型企业级应用开发实战 人工智能 copilot ai
AI人工智能与Copilot碰撞出的火花关键词:AI人工智能、Copilot、代码辅助、智能编程、人机协作、软件开发、技术创新摘要:本文深入探讨了AI人工智能与Copilot碰撞所产生的一系列效应。首先介绍了相关背景,包括目的、预期读者、文档结构和术语表。接着阐述了核心概念与联系,展示了其原理和架构的示意图及流程图。详细讲解了核心算法原理和具体操作步骤,并通过Python代码进行说明。同时给出了数
FPGA芯片厂商及关键的开发测试工具
Chip Design
xPU Chip Design fpga开发
以下是结合2025年技术动态整理的。一、FPGA芯片主要厂商及产品系列厂商芯片系列典型特点目标市场AMD/XilinxVersal,Kintex,Artix,Zynq高性能异构计算(AI引擎+FPGA+CPU)数据中心、5G、航空航天Intel(Altera)Stratix,Arria,Agilex,Cyclone高带宽内存集成(HBM),支持CXL协议网络加速、边缘计算LatticeCertus
网易云音乐会员优惠大揭秘,网友:太值了!
氧惠佣金真的高
在数字音乐时代,拥有一款高品质的音乐APP是音乐爱好者的必备之选。作为中国音乐市场的佼佼者,网易云音乐凭借其丰富的曲库、出色的推荐算法以及浓厚的社区氛围,吸引了大量用户。近日,网易云音乐推出了一系列会员优惠活动,让我们一起来了解一下吧!大家好,我是氧惠联合创始人七言导师,给大家推荐一款省钱更加赚钱的app——氧惠。氧惠是与以往完全不同的抖客+淘客app!2023全新模式,我的直推也会放到你下面。主
微算法科技研究量子视觉计算,利用量子力学原理提升传统计算机视觉任务的性能
计算机视觉,作为人工智能领域的一个重要分支,致力于模拟人类视觉系统对图像或视频等视觉数据的理解与分析能力。它涵盖了图像识别、目标检测、图像分割等一系列复杂任务,广泛应用于自动驾驶、医疗影像分析、安防监控等多个领域。然而,随着数据规模的不断膨胀和任务复杂度的日益提升,传统计算机视觉算法在处理大规模、高维度数据时遇到了性能瓶颈。微算法科技(NASDAQ:MLGO)研究量子视觉计算,探索量子计算与经典卷
浪漫与性感兼具|Julie Vino 2019婚纱系列
服装设计禅言
以色列设计师婚纱品牌JulieVino释出2019「Paris巴黎」婚纱系列,本季婚纱以巴黎城市为拍摄背景和主题诉求,打造浪漫与性感兼具的现代礼服。来源|CFW服装设计最懂得穿衣服的是哪个职业?——设计师有穿不完的衣服是哪个职业?——设计师走在时装最顶端的职业是?没错还是设计师,想学习服装设计成为设计师,可以私信小编,免费带粉丝入门!
Perl数组用法详细解析 架构
ExogFix
perl scala 开发语言 架构
Perl是一种功能强大的编程语言,广泛应用于各种领域。其中,数组是Perl中一种常用的数据结构,用于存储和操作一系列相关的数据。本文将详细解析Perl数组的用法,并提供相应的源代码示例。创建数组在Perl中,可以使用以下方式创建数组:#直接初始化数组my@array=(1,2,3,4,5)
一天学会超级玛丽小游戏_手把手教学_Java小游戏
62f5ecb72f71
超级玛丽是任天堂制作的一款小游戏,在的童年里一起玩这个游戏,大胡子,背带裤的马里奥,每关以马里奥在走到重点的前提下尽可能地收集金币。他在闯关过程中,会遇到怪物,可以通过踩死或者跳过。也会遇到深坑。给游戏增加了一定的难度。今天带大家用java制作制作这款小游戏,下面是课程介绍.课程介绍:在你的童年记忆里,是否有一个会蹦跳,会吃蘑菇的小人?超级玛丽是一款经典并且流行的小游戏,通过键盘来控制马里奥的移动
3步!用代码生成工具秒建SqlSugar Winform项目?手把手教学,小白也能轻松上手!
墨瑾轩
数据库学习 oracle 数据库
关注墨瑾轩,带你探索编程的奥秘!超萌技术攻略,轻松晋级编程高手技术宝库已备好,就等你来挖掘订阅墨瑾轩,智趣学习不孤单即刻启航,编程之旅更有趣(对比传统开发效率:人工写代码vs魔法生成器,谁才是真正的“代码魔法师”?)代码生成工具——程序员的“魔法棒”你有没有试过用Excel表格生成代码?或者像搭积木一样拼出一个完整的Winform项目?SqlSugar+代码生成工具(比如Database2Shar
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