文章目录
前言
最近写了一个词典网站的爬虫,响应以及获取数据的方式非常的简单,本以为会是个轻松的爬虫脚本,没曾想出了很多意料之外的问题,这样使得我对代码本身有了更加清晰的认知。
提示:以下是本篇文章正文内容,下面案例可供参考
一、分析数据源
在爬取所需的资源时,首当其冲的就是要确认数据来源,数据具体在什么位置,数据的目录页,分类所进入的逻辑是什么等等。笔者在编写一个爬虫脚本之前往往会先思考这个问题,只要将逻辑考虑的清晰顺畅,后面我们所做的代码编写会轻松很多。
目标网站:https://ctext.org/zhs
思路分析
首先进入主页,引入眼帘的是如下页面:
左侧是网站的目录,我们的目的是拿到该词典的古文双译句子。按照该网站的目录情况,似乎该词典网站的数据大体上分为"先秦两汉"和"汉代之后"两类,我们尝试点进去看看。
注意这里url的变化,在进入"先秦两汉"页面后,首先网站的url发生了变化,多了一个/pre-qin-and-han/部分,这次可以初步判定这是一个get请求。并且在切换英文繁体的时候,仅是ulr的后缀发生了变化,而且我们发现在英文和繁体的页面中,会直接带每一句其相应的英文翻译,这样的话,我们最终的目标页就确定了,我们只需要进入该页面就可以拿到我们所需的内容。
再其次我们发现,在"先秦两汉"的页面中,似乎其下的分类,例如:论语,孟子等等都是以及加载好了,似乎我们并不需要经过"论语"这一部分。我们在"先秦两汉"的页面中就可以拿到每一部古文经典中每一篇的url。我们打开F12调试验证一下:
果然,验证了之前我们的猜想,可是这里似乎还多了例如:儒家,墨家,道家等这样“先秦两汉”次级目录下的url,并且在这里的页面规则下,我们利用xpath提取相应的href时会包含它们。我们初步的思路是跳过这一次级目录,直接取"儒家,墨家,道家等"其下的每一篇的url,那会不会有影响呢?我们分别点进两种页面看看。
答案是不会,即使我们将"先秦两汉"页面中的包含了"论语,孟子"等url提取并访问,但在我们进去真正所需页面,例如"儒家"的次级目录中的"论语"时,编写的xpath才能提取到真正所需的内容,而其他页面获取为空。
截止目前,我们的思路是:先进入"先秦两汉"的页面,再访问"先秦两汉"目录下的所有url,随后在进入到例如:“论语”,"孟子"页面中的每一篇文章的url,这样即可拿到目标资源。
用个实例来理一理:
https://ctext.org/pre-qin-and-han/ens → https://ctext.org/analects/ens → https://ctext.org/analects/xue-er/ens
大致上是这样的顺序。
在访问页面的时候,我发现似乎并不是每一篇文章都有英文翻译,并且我还发现个别文章比我们预想的访问顺序会少一级,如图:
我们访问"独断"时,按照顺序显示的页面应该是"独断"目录下的每一篇文章的url,但这这里却直接显示出了目标内容,如图:
这是一个小坑,如果忽略这一点可能会导致我们爬取的数据不全,产生遗漏。所幸通过观察,我发现,带有中英文翻译的页面,在我们访问的第二级,也就是例如:"论语"的页面,"论语"页面中是其下每一篇文章的url,如果该篇文章内含中英文翻译,那该篇文章的url的标题结构就是 中文 + “-” + “英文”,而不带有英文翻译的 标题结构仅是 中文。如图:
似乎逻辑思路已经完善了,但在我编写代码完后发现中英文的数量对不上,中文会比英文的数量要多,这让我非常难受,在仔细排查一番后我发现,问题是出在最后的目标页面上,如图:
我们发现,如图上这个作为例子,安装思路,我们先进入 https://ctext.org/pre-qin-and-han/ens,然后进入 https://ctext.org/mozi/ens,而在墨子的页面中,其图示路径也不同,如图:
若是点箭头所指的url,一般是如图下所示的页面:
但"墨子"的"卷十一"却不同,如图:
"卷十一"的内容不是次级url而是目标文本,并且其文本中英文并不是一一对应。这个网站的页面属实有点乱,不过好在确定了思路,下面是代码部分。
二、代码部分
代码如下(示例):
import scrapy
from redis import Redis
from lxml.html import etree
from html import unescape
from scrapy import cmdline, selector, Request
class ClassicalSpider(scrapy.Spider):
name = 'classical'
start_urls = ['https://ctext.org/pre-qin-and-han/ens','https://ctext.org/post-han/ens']
conn = Redis(host='127.0.0.1', encoding='utf-8', port=6379)
def parse(self, response):
url_primary = response.xpath('//*[@id="content3"]/a/@href').getall()
for i in url_primary:
yield response.follow(url=i, callback=self.parse_detail,dont_filter=True)
def parse_detail(self, response):
b = response.body
page = etree.HTML(b)
page = unescape(page)
whole_url = []
whole_title = []
Available_url = []
Available_title = []
content_chs_list = []
content_ens_list = []
for bad in page.xpath('//*[@id="content3"]/table[@border="0"]//td[@class="etext"]/p'):
bad.getparent().remove(bad)
for y in page.xpath('//table[@border="0"]'):
content_chs = [x.xpath('string(.)').strip() for x in y.xpath('.//td[@class="ctext"]') if
x.xpath('string(.)').strip()]
content_ens = [x.xpath('string(.)').strip() for x in y.xpath('.//td[@class="etext"]') if
x.xpath('string(.)').strip()]
if len(content_chs) == len(content_ens):
for i in content_chs:
content_chs_list.append(i.strip())
for i in content_ens:
content_ens_list.append(i.strip())
content_secondary = response.xpath('//*[@id="content2"]/a/@href').extract()
for i in content_secondary:
whole_url.append(i)
title_primary = response.xpath('//*[@id="content2"]/a/text()').getall()
for i in title_primary:
whole_title.append(i)
for whole_title,whole_url in zip(whole_title,whole_url):
if whole_title.find('-')>=0:
Available_url.append(whole_url)
Available_title.append(whole_title)
for ture_url in Available_url:
yield response.follow(url=ture_url, callback=self.parse_page,meta={'chs': content_chs_list,'ens':content_ens_list})
def parse_page(self, response):
content = response.body
page = etree.HTML(content)
page = unescape(page)
content_chs_list = response.meta['chs']
content_ens_list = response.meta['ens']
for bad in page.xpath('//*[@id="content3"]/table[@border="0"]//td[@class="etext"]/p'):
bad.getparent().remove(bad)
for y in page.xpath('//table[@border="0"]'):
content_chs = [x.xpath('string(.)').strip() for x in y.xpath('.//td[@class="ctext"]') if
x.xpath('string(.)').strip()]
content_ens = [x.xpath('string(.)').strip() for x in y.xpath('.//td[@class="etext"]') if
x.xpath('string(.)').strip()]
if len(content_chs) == len(content_ens):
for i in content_chs:
content_chs_list.append(i.strip())
for i in content_ens:
content_ens_list.append(i.strip())
if len(content_chs_list)==len(content_ens_list):
ch_url = (response.url).replace('/ens','')
yield response.follow(url=ch_url, callback=self.parse_ch,meta={'chs': content_chs_list,'ens':content_ens_list})
def parse_ch(self, response):
item = {}
content_chs_list = response.meta['chs']
content_ens_list = response.meta['ens']
content = response.body
page = etree.HTML(content)
page = unescape(page)
content_ch_list = []
for bad in page.xpath('//*[@id="content3"]/table[@border="0"]//td[@class="etext"]/p'):
bad.getparent().remove(bad)
for y in page.xpath('//table[@border="0"]'):
content_ch = [x.xpath('string(.)').strip() for x in y.xpath('.//td[@class="ctext"]') if
x.xpath('string(.)').strip()]
content_ens = [x.xpath('string(.)').strip() for x in y.xpath('.//td[@class="etext"]') if
x.xpath('string(.)').strip()]
if len(content_ch) == len(content_ens):
for i in content_ch:
content_ch_list.append(i.strip())
# content_ch = [x.xpath('string(.)') for x in page.xpath('//*[@id="content3"]/table[@border="0"]//td[@class="ctext"]')]
# for i in content_ch:
# if i.strip():
# content_ch_list.append(i.strip())
if len(content_chs_list) == len(content_ens_list) == len(content_ch_list):
for chs,ch,ens in zip(content_chs_list,content_ch_list,content_ens_list):
item['chs'] = chs
item['ch'] = ch
item['ens'] = ens
# print(item)
yield item
if __name__ == '__main__':
cmdline.execute(["scrapy", "crawl", "classical"])
总结
在完成一个网站代码的编写的同时,多关注逻辑部分以及代码本身,即使是简单的网站,不同代码的执行效率等也是不同的,这正是我们需要学习进步的点。