使用Scrapy根据DOI下载文献到本地
Scrapy框架专门提供了用于文件下载的FilesPipline
和用于图片下载的ImagePipline
爬取策略
- 从Mysql数据库中提取DOI号
- 拼接url:‘http://www.sci-hub.ren/’+doi 指向doi对应的pdf页面
- 在该页面中通过xpath和正则表达式,将指向pdf的url提取出来,交付给pipline进行下载
具体步骤
- 设置setting.py
# setting.py # 框架自动生成无需修改 BOT_NAME = 'DoiDown' SPIDER_MODULES = ['DoiDown.spiders'] NEWSPIDER_MODULE = 'DoiDown.spiders' # 设置USER_AGENT USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36" # 不遵从robots协议 ROBOTSTXT_OBEY = False # 设置日志等级 LOG_LEVEL = 'ERROR' # 将日志保存到日志文件 LOG_FILE = './log.log' # 设置下载延迟 DOWNLOAD_DELAY = 3 # 开启管道并设置多管道优先级 ITEM_PIPELINES = { 'DoiDown.pipelines.initPipeline': 200, 'DoiDown.pipelines.fileDown': 300 } # 设定FilePipline的文件保存路径 FILES_STORE = './PDF'
- 定义item
# items.py import scrapy class DoidownItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() file_urls = scrapy.Field() files = scrapy.Field() pass
file_urls
和file
都是item的字段。FilesPipline
要求file_urls
必须是一个列表,存的是待下载文件的url。文件下载完成之后,file
包含了文件的路径、文件校验、文件的url等信息。 - 编写spider
# doi_down.py import pymysql import scrapy from scrapy import Request import re from ..items import DoidownItem class DoiDownSpider(scrapy.Spider): name = 'doi_down' # allowed_domains = ['sci-hub.ren'] connect = None cursor = None # start_urls = ['http://sci-hub.ren/'] def start_requests(self): # 自定义开始爬取的url。spider从这个函数开始执行。 self.connect = pymysql.connect( host="localhost", port=3306, user="root", passwd="xxxxxx", db="web_of_science") self.cursor = self.connect.cursor() self.cursor.execute("select doi from wos_document;") doi_lists = list(self.cursor.fetchall()) for doi in doi_lists[:50]: if doi[0] is None: print('该DOI为空...') else: detail_url = 'https://www.sci-hub.ren/' + doi[0] print(detail_url) # 发起url请求,并指定处理响应的回调函数 yield Request(detail_url, callback=self.parse) self.connect.close() def parse(self, response): # 该函数用于解析响应数据 print('解析中!!!') detail_url_list = response.xpath('//*[@id="buttons"]/ul/li[2]/a/@onclick') item = DoidownItem() item['file_urls'] = [] if len(detail_url_list) == 0: print('*' * 10 + '已自动跳转第三方网站,不予处理!!!' + '*' * 10) else: # 提取第一个目标Selector的内容 target = detail_url_list.extract_first() find_url = re.compile(r"href='(.*?)'") target_url = re.findall(find_url, target)[0] if target_url[0] != 'h': target_url = 'https:' + target_url item['file_urls'].append(target_url) # 这里解析到了pdf文件对应的url;yield的作用是交付给pipline.py处理 yield item
- 自定义pipelines
之前已经在import re # useful for handling different item types with a single interface from scrapy import Request from scrapy.pipelines.files import FilesPipeline class initPipeline: def open_spider(self, spider): print('*' * 10 + '开始下载...' + '*' * 10) def process_item(self, item, spider): return item def close_spider(self, spider): print('*' * 10 + '爬取完毕' + '*' * 10) class fileDown(FilesPipeline): def get_media_requests(self, item, info): # 向FilesPipline提交url地址,进行文件下载 for url in item['file_urls']: yield Request(url) def file_path(self, request, response=None, info=None): # 设置保存的文件名 find_name = re.compile(r"/(.*?)?download=true") file_name = re.findall(find_name, request.url)[0] file_name = file_name.split('/')[-1] file_name = file_name[0:len(file_name) - 1] print(file_name, '正在下载!!!') return file_name
settings.py
中设定了initPipline
优先级较高。在initPipline
中自定义了一个管道,爬虫程序从open_spider
开始执行。然后执行spider,也就是doi_down.py
。程序执行完close_spider
后 结束。
使用FilesPipeline
下载文件,需要在管道中从scrapy.pipelines.files
import进来。然后作为参数传入自定义的用于下载文件的管道类中。fileDown
中get_media_requests
是用于发起下载请求的函数;file_path
是用于设定保存文件名的函数。这两个函数的函数名是固定的,也就是说需要用FilesPipeline
自定义的管道,要自定义请求的发起和保存的文件名的设定,对应的函数名必须这样命名。