目录
最近有小伙伴后台跟我说,临近毕业,写毕业论文需要上知网查找大量的文献,但是一篇一篇看文献信息以及文献摘要又有点麻烦,能不能让我写一个爬虫去批量获取文献相关信息
我一听好家伙,当初我写毕业论文的时候也是饱经查阅文献的折磨,深知那种痛苦
但是知网作为国内知名的文献数据库之一,有着极其复杂的反爬虫机制,例如动态JS、iframe、验证码等等,不是说想爬就能爬的
像我之前采用 requests 模拟请求的方法来爬取的话难度很大,一个不小心就有可能被封 IP
本篇文章就主要介绍该如何使用 **Selenium **来巧爬知网
selenium 是一个自动化测试工具,可以用来进行 web 自动化测试
selenium 本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器
爬虫中用到 selenium 主要是为了解决 requests 无法直接执行 JavaScript 代码等问题
下面就来介绍下 selenium 基础用法
声明浏览器对象
Selenium 支持非常多的浏览器,如Chrome、Firefox、Edge 等
我们只要首先下载好相应浏览器的驱动(webdriver)到python主目录中,或者加入环境变量即可
#Firefox浏览器驱动:
https://link.zhihu.com/?target=https%3A//github.com/mozilla/geckodriver/releases
#Chrome浏览器驱动:
https://registry.npmmirror.com/binary.html?path=chromedriver/
#IE浏览器驱动:IEDriverServer
https://link.zhihu.com/?target=http%3A//selenium-release.storage.googleapis.com/index.html
#Edge浏览器驱动:MicrosoftWebDriver
https://link.zhihu.com/?target=https%3A//developer.microsoft.com/en-us/microsoft-edge/tools/webdriver
下载好驱动之后就可以浏览器初始化了
from selenium import webdriver
browser = webdriver.Chrome()
browser = webdriver.Firefox()
browser = webdriver.Edge()
browser = webdriver.Safari()
访问页面
我们可以用 get() 方法来请求一个网页,传入参数链接URL
browser.get('https://blog.csdn.net/s_alted')
查找元素
#根据 id 查找
find_element_by_id()
#根据 name 查找
find_element_by_name()
#根据 class name 查找
find_element_by_class_name()
#根据 Tag name 查找
find_element_by_tag_name()
#根据 完整超链接 查找
find_element_by_link_text()
#根据 部分超链接 查找
find_element_by_partial_link_text()
#根据 xpath 查找
find_element_by_xpath()
#根据 css选择器 查找
find_element_by_css_selector()
PS:
上面的 element 变成 elements(例如find_elements_by_id)就是找到所有满足的条件,然后返回数据
等待页面加载完成
有显式等待和隐式等待
显式等待使 WebdDriver 等待某个条件成立时继续执行,否则在达到最大时长时抛出超时异常(TimeoutException)
WebDriverWait 类是由 WebDirver 提供的等待方法。在设置时间内,默认每隔一段时间检测一次当前页面元素是否存在,如果超过设置时间检测不到则抛出异常
常用浏览器操作
在找到浏览器相应元素的位置之后,我们就需要进行一些交互动作,例如双击、点击、输入、获取网页源码等等
element = find_element_by_id(ID,id)
element.send_keys(Keys.CONTROL,'c') # 复制
element.send_keys("hello") #传入 hello
element.clear() # 清除输入框
element.click() # 单击元素
element.text # 获取元素文本信息
element.get_attribute('href') # 获取元素属性
有了上面这些基本用法,就可以开始编写代码程序了!
知网官网:https://www.cnki.net/
按照这位同学的需求,需要进入到知网官网之后——>点击高级搜索图标

然后在文献来源输入框处输入相关内容,然后点击检索图标


因此可以得到如下步骤:
进入官网点击高级搜索——>在文献来源处输入信息——>点击检索
通过 F12 检查浏览器页面,得到高级搜索图标和输入框以及检索图标元素的 xpath 分别如下:

#高级搜索 xpath
/html/body/div[2]/div[2]/div/div[2]/a[1]
#输入框 xpath
/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/dl/dd[3]/div[2]/input
#检索 xpath
/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input
接着我们发现结果页(以搜索管理世界为例)
共找到 8550 条结果,300页,每一页包含 20 条文献条目
每个条目包含题目、作者、来源等信息
通过对当前页面分析,发现每条文献条目的 xpath 是有规律的
#题名
/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[1]/td[2]
#作者
/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[1]/td[3]
tr[1] 表示本页第一条条目,而 td[2] 中的2-6 分别代表作者、来源、发表时间和数据库
我们在当前页面是无法获取到文献的摘要、关键字等信息,需要进一步点击进入相关文献条目
进入到相关文献页面之后,根据 class name来获取摘要、关键字、是否为CSSCI 这些元素


完成以上知网页面的分析后,我们就可以根据需求开始写代码了!
导入所需包
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
创建浏览器对象
这里我用的是 Edge 浏览器
# get直接返回,不再等待界面加载完成
desired_capabilities = DesiredCapabilities.EDGE
desired_capabilities["pageLoadStrategy"] = "none"
# 设置 Edge 驱动器的环境
options = webdriver.EdgeOptions()
# 设置 Edge 不加载图片,提高速度
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 创建一个 Edge 驱动器
driver = webdriver.Edge(options=options)
传入 url 参数然后模拟对浏览器进行人为操作
适当的加入 time.sleep() 方法,等待页面加载完成
不然页面还没完全加载就执行下一步操作的话会报错
# 打开页面
driver.get("https://kns.cnki.net/kns8/AdvSearch")
time.sleep(2)
# 传入关键字
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, '''//*[@id="gradetxt"]/dd[3]/div[2]/input'''))).send_keys(theme)
time.sleep(2)
# 点击搜索
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input"))).click()
time.sleep(3)
# 点击切换中文文献
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[3]/div[1]/div/div/div/a[1]"))).click()
time.sleep(3)
获取总文献数和页数
res_unm = WebDriverWait(driver, 100).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em"))).text
# 去除千分位的逗号
res_unm = int(res_unm.replace(",", ''))
page_unm = int(res_unm / 20) + 1
print(f"共找到 {res_unm} 条结果, {page_unm} 页。")
对结果页进行解析
def crawl(driver, papers_need, theme):
# 赋值序号, 控制爬取的文章数量
count = 1
# 当爬取数量小于需求时,循环网页页码
while count <= papers_need:
# 等待加载完全,休眠3S
time.sleep(3)
title_list = WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "fz14")))
# 循环网页一页中的条目
for i in range(len(title_list)):
try:
if count % 20 != 0:
term = count % 20 # 本页的第几个条目
else:
term = 20
title_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[2]"
author_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[3]"
source_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[4]"
date_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[5]"
database_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[6]"
title = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, title_xpath))).text
authors = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, author_xpath))).text
source = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, source_xpath))).text
date = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, date_xpath))).text
database = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, database_xpath))).text
# 点击条目
title_list[i].click()
# 获取driver的句柄
n = driver.window_handles
# driver切换至最新生产的页面
driver.switch_to.window(n[-1])
time.sleep(3)
# 开始获取页面信息
title = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h1"))).text
authors = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h3[1]"))).text
institute = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h3[2]"))).text
abstract = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "abstract-text"))).text
try:
keywords = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "keywords"))).text[:-1]
cssci = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[1]/div[1]/a[2]"))).text
except:
keywords = '无'
cssci = 'NULL'
url = driver.current_url
# 写入文件
res = f"{count}\t{title}\t{authors}\t{cssci}\t{institute}\t{date}\t{source}\t{database}\t{keywords}\t{abstract}\t{url}".replace(
"\n", "") + "\n"
print(res)
with open(f'{theme}.tsv', 'a', encoding='gbk') as f:
f.write(res)
except:
print(f" 第{count} 条爬取失败\n")
# 跳过本条,接着下一个
continue
finally:
# 如果有多个窗口,关闭第二个窗口, 切换回主页
n2 = driver.window_handles
if len(n2) > 1:
driver.close()
driver.switch_to.window(n2[0])
# 计数,判断篇数是否超出限制
count += 1
if count == papers_need:
break
else:
# 切换到下一页
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "//a[@id='PageNext']"))).click()
结果展示:
结果是一个以制表符分隔的表格文件(用 excel 打开),其中包含了论文的基本信息,包括:题目、作者、是否 CSSCI、来源、摘要等
完整代码如下:
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from urllib.parse import urljoin
def open_page(driver, theme):
# 打开页面
driver.get("https://kns.cnki.net/kns8/AdvSearch")
time.sleep(2)
# 传入关键字
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, '''//*[@id="gradetxt"]/dd[3]/div[2]/input'''))).send_keys(theme)
time.sleep(2)
# 点击搜索
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div/div[2]/div/div[1]/div[1]/div[2]/div[2]/input"))).click()
time.sleep(3)
# 点击切换中文文献
WebDriverWait(driver, 100).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[3]/div[1]/div/div/div/a[1]"))).click()
time.sleep(3)
# 跳转到第三页
# WebDriverWait(driver, 100).until(
# EC.presence_of_element_located((By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[2]/a[2]"))).click()
# time.sleep(3)
# 获取总文献数和页数
res_unm = WebDriverWait(driver, 100).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[3]/div[2]/div[2]/div[2]/form/div/div[1]/div[1]/span[1]/em"))).text
# 去除千分位里的逗号
res_unm = int(res_unm.replace(",", ''))
page_unm = int(res_unm / 20) + 1
print(f"共找到 {res_unm} 条结果, {page_unm} 页。")
return res_unm
def crawl(driver, papers_need, theme):
# 赋值序号, 控制爬取的文章数量
count = 1
# 当爬取数量小于需求时,循环网页页码
while count <= papers_need:
# 等待加载完全,休眠3S
time.sleep(3)
title_list = WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located((By.CLASS_NAME, "fz14")))
# 循环网页一页中的条目
for i in range(len(title_list)):
try:
if count % 20 != 0:
term = count % 20 # 本页的第几个条目
else:
term = 20
title_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[2]"
author_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[3]"
source_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[4]"
date_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[5]"
database_xpath = f"/html/body/div[3]/div[2]/div[2]/div[2]/form/div/table/tbody/tr[{term}]/td[6]"
title = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, title_xpath))).text
authors = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, author_xpath))).text
source = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, source_xpath))).text
date = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, date_xpath))).text
database = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, database_xpath))).text
# 点击条目
title_list[i].click()
# 获取driver的句柄
n = driver.window_handles
# driver切换至最新生产的页面
driver.switch_to.window(n[-1])
time.sleep(3)
# 开始获取页面信息
title = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h1"))).text
authors = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h3[1]"))).text
institute = WebDriverWait(driver, 10).until(EC.presence_of_element_located(
(By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[3]/div/h3[2]"))).text
abstract = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "abstract-text"))).text
try:
keywords = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "keywords"))).text[:-1]
cssci = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "/html/body/div[2]/div[1]/div[3]/div/div/div[1]/div[1]/a[2]"))).text
except:
keywords = '无'
cssci = 'NULL'
url = driver.current_url
# 获取下载链接
# link = WebDriverWait( driver, 10 ).until( EC.presence_of_all_elements_located((By.CLASS_NAME ,"btn-dlcaj") ) )[0].get_attribute('href')
# link = urljoin(driver.current_url, link)
# 写入文件
res = f"{count}\t{title}\t{authors}\t{cssci}\t{institute}\t{date}\t{source}\t{database}\t{keywords}\t{abstract}\t{url}".replace(
"\n", "") + "\n"
print(res)
# with open(f'CNKI_{theme}.tsv', 'a', encoding='gbk') as f:
# f.write(res)
count += 1
if count > papers_need:
break
except:
print(f" 第{count} 条爬取失败\n")
# 跳过本条,接着下一个
continue
finally:
# 如果有多个窗口,关闭第二个窗口, 切换回主页
n2 = driver.window_handles
if len(n2) > 1:
driver.close()
driver.switch_to.window(n2[0])
# 计数,判断需求是否足够
# count += 1
if count > papers_need:
break
else:
# 切换到下一页
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "//a[@id='PageNext']"))).click()
def webserver(theme):
# get直接返回,不再等待界面加载完成
desired_capabilities = DesiredCapabilities.EDGE
desired_capabilities["pageLoadStrategy"] = "none"
# 设置微软驱动器的环境
options = webdriver.EdgeOptions()
# 设置chrome不加载图片,提高速度
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 创建一个微软驱动器
driver = webdriver.Edge(options=options)
# 设置所需篇数
papers_need = 41
res_unm = int(open_page(driver, theme))
# 判断所需是否大于总篇数
papers_need = papers_need if (papers_need <= res_unm) else res_unm
return driver, papers_need, theme
if __name__ == "__main__":
# 输入需要搜索的内容
# theme = input("请输入你要搜索的期刊名称:")
print('1')
theme = "中国人口·资源与环境"
driver, papers_need, theme = webserver(theme)
print("所需篇数: ", papers_need)
crawl(driver, papers_need, theme)
# 关闭浏览器
driver.close()
网页加载太慢导致元素查找出错
网络并不是可靠的,我在调试程序的时候往往出现网页加载过慢导致元素查找出错
desired_capabilities = DesiredCapabilities.EDGE
desired_capabilities["pageLoadStrategy"] = "none"
在需要等待网页加载完全之后才能执行下一步骤的地方加上 time.sleep() 方法休眠几秒,既可以等待页面加载,也可以防止爬取太快被封IP
代码逻辑出错导致爬取不了 20 倍页数的第 20 条文献信息
刚开始写的时候,逻辑不够准确,导致第20页、40页、60页(20整数倍)的第20条文献爬取不了
后面加了层判断:
if count % 20 != 0:
term = count % 20 # 本页的第几个条目
else:
term = 20 # 本页的第20个条目
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
有没有办法在这个简单的get方法中添加超时选项?我正在使用法拉第3.3。Faraday.get(url)四处寻找,我只能先发起连接后应用超时选项,然后应用超时选项。或者有什么简单的方法?这就是我现在正在做的:conn=Faraday.newresponse=conn.getdo|req|req.urlurlreq.options.timeout=2#2secondsend 最佳答案 试试这个:conn=Faraday.newdo|conn|conn.options.timeout=20endresponse=conn.get(url
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我安装了ruby版本管理器,并将RVM安装的ruby实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby。有没有办法让emacs像shell一样尊重ruby的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur