共计 7082 个字符,预计需要花费 18 分钟才能阅读完成。
前言
上一篇文章中爬取了爬虫练习平台的所有 ssr 网站,都是比较简单的,没有反爬措施,这次来爬一下前面的 spa 系列。
环境筹备
这里沿用了上篇文章的环境和设置,就不从新搭建环境了。
开始爬取
spa1
spa1 阐明如下:
电影数据网站,无反爬,数据通过 Ajax 加载,页面动静渲染,适宜 Ajax 剖析和动静页面渲染爬取。
还是无反爬,Ajax 加载数据,那么最简略的办法就是关上 Chrome 控制台,找 xhr 申请。
一共有两个申请,第一个申请通过了 301 重定向,所以理论接管到数据的是第二个申请。
查看数据,根本数据都有了,然而没有作者信息,轻易点击一个电影,查看详细信息,查找接口。
通过 id 和另一个 API 接口获取详细信息,能够找到作者,OK,开始写代码。
其余代码和之前从 HTML 中解析数据的逻辑统一,只是在解析办法上不一样。
class SPA1Spider(scrapy.Spider):
name = "spa1"
def start_requests(self):
urls = [f'https://spa1.scrape.center/api/movie/?limit=10&offset={a}' for a in range(0, 100, 10)
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response, **kwargs):
result = response.json()
for a in result['results']:
item = SPA1Item()
item['title'] = a['name'] + a['alias']
item['fraction'] = a['score']
item['country'] = '、'.join(a['regions'])
item['time'] = a['minute']
item['date'] = a['published_at']
yield Request(url=response.urljoin(f'/api/movie/{a["id"]}/'), callback=self.parse_person,
meta={'item': item})
def parse_person(self, response):
result = response.json()
item = response.meta['item']
item['director'] = result['directors'][0]['name']
yield item
因为是通过 API 接口进行遍历的,所以应用总的页数进行循环就能够失去所有的起始 URL。
而后通过读取 JSON 格局的响应,顺次循环获取数据,而后进入到详情页获取作者信息,最初返回数据。
残缺代码详见 https://github.com/libra146/learnscrapy/tree/spa1
spa2
spa2 阐明如下:
电影数据网站,无反爬,数据通过 Ajax 加载,数据接口参数加密且有工夫限度,适宜动静页面渲染爬取或 JavaScript 逆向剖析。
和 spa1 相比多了一个数据接口参数加密,先关上控制台看一下多了什么参数。
接口没有变,只是多了一个 token 参数,接下来来寻找一下这个 token 是怎么生成的。
既然是网络申请,而且 URL 中蕴含 token,那么剖析 token 起源就要从 URL 断点动手。
下断点,但凡蕴含 token 字符串的 URL 全副会断下,刷新页面,断点断下,格式化 js 代码,能够看到断下的地位为 send 函数,用来发送申请。
依照右侧调用栈顺次往下看,发现在 onFetchData 函数左近发现了 token 参数,勾销 URL 断点,在 token 的生成地位下断点。从新刷新页面,断点断下。
选中断下的这行代码,看下 e 的值正好就是 token,单步进入代码查看具体执行了什么。
看不到具体的信息,应该是在执行 Object(i["a"])
,步出,持续单步跟。
前边的 Object(i[“a”]) 执行后应该是一个函数,传入的参数是以后的 URL,单步进入。
进入到了新的代码块,这次能够看到 sha1 函数和 base64 函数,应该是加密函数所在位置了,单步走一下看都用到了什么信息。
首先获取了以后的工夫戳信息,而后从 arguments 中获取传入的 URL 参数。
循环完之后将以后的 URL 和工夫戳都压入 r 这个数组中。
将数组应用逗号连接起来,计算 sha1 值。
将计算结果和工夫戳再次应用逗号连接起来,而后通过 base64 编码失去 token。
别急,还没完,获取首页信息的 token 算法曾经有了,然而获取详细信息的页面 URL 是这样的:
token 应该和上一个申请是一样的,然而红框中这一段信息上个申请没有返回,那么只可能是本地生成的,我解码看了下,后果是:ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb1
一堆看起来像乱码的货色,还得剖析它是怎么来的,既然都用到了 token,那么它在计算完前边这段信息之后必定会去计算 token 的,所以持续下 token 断点而后往前找就能够了,话不多说持续下断点。
依然是断在了 send 函数上,持续寻找相熟的 onFetchData 函数,相熟的 token,勾销 URL 断点,在 token 生成这一行下断点,刷新页面。
能够看到在计算 token 时 URL 曾经被计算好了,而后网上看,正好 URL 的值来自于 e 这个变量,e 的值来自于 o 这个函数,OK,从新下断点,刷新页面。
断下,然而看样子 o 函数只是格式化了 key 参数,key 是在之前就计算好了,这里并没有计算 key 的流程,看了下其余的调用栈,应该是 vue 被混同之后的样子,太乱了,此路不通,换条路走。
既然 key 是提前计算好的,那么全局搜寻 key,看看是在哪个函数中给 key 赋值的。
一共 9 个匹配,挨个找也不多,其余的都排除掉了,找到了这个,相熟的 router-link(这不是 vue 中生成 a 标签办法嘛),相熟的 detail(URL 中蕴含这个字符串),相熟的 key,应该是它没错了,下断点,刷新页面。
传入了以后电影的 id 值,因为我点击的是第一个电影,所以它的 id 是 1,单步步入。
加密形式高深莫测,这串乱码居然是硬编码进去的,哈哈,很坑有木有。所以加密形式就是这串硬编码的字符串加上电影的 id 而后应用 base64 编码即可。
好了,两个加密办法都剖析好了,能够写代码了,残缺代码如下:
class SPA2Spider(scrapy.Spider):
name = "spa2"
@staticmethod
def sign(url):
t = str(int(time.time()) - random.randint(1, 20))
s = ','.join([url, t])
return base64.b64encode(','.join([sha1(s.encode()).hexdigest(), t]).encode()).decode()
def start_requests(self):
urls = [f'http://spa2.scrape.center/api/movie/?limit=10&offset={a}&token={self.sign("/api/movie")}' for a in
range(0, 100, 10)
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response, **kwargs):
result = response.json()
for a in result['results']:
item = SPA2Item()
item['title'] = a['name'] + a['alias']
item['fraction'] = a['score']
item['country'] = '、'.join(a['regions'])
item['time'] = a['minute']
item['date'] = a['published_at']
s = 'ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb'
detail = base64.b64encode((s + str(a["id"])).encode()).decode()
yield Request(url=response.urljoin(f'/api/movie/{detail}/?token={self.sign("/api/movie/"+ detail)}'),
callback=self.parse_person,
meta={'item': item})
def parse_person(self, response):
result = response.json()
item = response.meta['item']
item['director'] = result['directors'][0]['name']
yield item
为了避免爬取时工夫戳都是一样的,加了一点的随机偏移。
残缺代码详见 https://github.com/libra146/learnscrapy/tree/spa2
spa3
spa3 阐明如下:
电影数据网站,无反爬,数据通过 Ajax 加载,无页码翻页,下拉至底部刷新,适宜 Ajax 剖析和动静页面渲染爬取。
无加密,无反爬,然而也没有页码,也就是说不能间接结构好所有的起始 URL,要在爬取的过程中去计算,代码如下:
class SPA3Spider(scrapy.Spider):
name = "spa3"
offset = 0
url = 'https://spa3.scrape.center/api/movie/?limit=10&offset=%s'
def start_requests(self):
urls = [self.url % self.offset]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response, **kwargs):
result = response.json()
for a in result['results']:
item = SPA3Item()
item['title'] = a['name'] + a['alias']
item['fraction'] = a['score']
item['country'] = '、'.join(a['regions'])
item['time'] = a['minute']
item['date'] = a['published_at']
yield Request(url=response.urljoin(f'/api/movie/{a["id"]}/'), callback=self.parse_person,
meta={'item': item})
if self.offset < result['count']:
# 因为每次申请限度 10 条数据,所以每次将偏移量 +10
self.offset += 10
yield Request(url=self.url % self.offset, callback=self.parse)
def parse_person(self, response):
result = response.json()
item = response.meta['item']
item['director'] = result['directors'][0]['name']
yield item
因为没有页码,所以起始 URL 的偏移只能从 0 开始,依据 limit 逐步增大,进行条件就是偏移量大于响应中的 count 字段的值。其余的逻辑和 spa1 是一样的,不多说。
残缺代码详见 https://github.com/libra146/learnscrapy/tree/spa3
spa4
spa4 阐明如下:
新闻网站索引,无反爬,数据通过 Ajax 加载,无页码翻页,适宜 Ajax 剖析和动静页面渲染抓取以及智能页面提取剖析。
spa4 和 spa3 基本上的逻辑是统一的,只是爬取内容不一样。
class SPA4Spider(scrapy.Spider):
name = "spa4"
offset = 0
limit = 100
url = f'https://spa4.scrape.center/api/news/?limit={limit}&offset=%s'
def start_requests(self):
urls = [self.url % self.offset]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response, **kwargs):
result = response.json()
print(response.url)
for a in result['results']:
item = SPA4Item()
item['code'] = a['code']
item['published_at'] = a['published_at']
item['title'] = a['title']
item['updated_at'] = a['updated_at']
item['url'] = a['url']
item['website'] = a['website']
# 新闻网址都是内部链接,抓取形式都不一样,就不抓取具体信息了
yield item
if int(result['count']) > self.offset:
self.offset += self.limit
yield Request(url=self.url % self.offset, callback=self.parse)
因为 spa4 数据量太大,这里为了学习就不全副爬取了,点到为止。
残缺代码详见 https://github.com/libra146/learnscrapy/tree/spa4
spa5
spa5 阐明如下:
图书网站,无反爬,数据通过 Ajax 加载,有翻页,适宜大批量动静页面渲染抓取。
图书网站,须要爬取的内容又不一样了,须要新建一个新的 item,其余的内容和下面的爬取办法是统一的。
class SPA5Spider(scrapy.Spider):
name = "spa5"
offset = 0
limit = 18
url = f'https://spa5.scrape.center/api/book/?limit={limit}&offset=%s'
def start_requests(self):
urls = [self.url % self.offset]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response, **kwargs):
result = response.json()
print(response.url)
for a in result['results']:
item = SPA5Item()
item['title'] = a['name']
item['author'] = '/'.join(a['authors'] or [])
yield Request(url=f'https://spa5.scrape.center/api/book/{a["id"]}/', callback=self.parse2,
meta={'item': item})
if int(result['count']) > self.offset:
self.offset += self.limit
yield Request(url=self.url % self.offset, callback=self.parse)
def parse2(self, response):
item = response.meta['item']
result = response.json()
item['price'] = result['price'] or 0
item['time'] = result['published_at']
item['press'] = result['publisher']
item['page'] = result['page_number']
item['isbm'] = result['isbn']
yield item
先从根本数据中记录下 title 和 author,而后再进入到详细信息页面获取到书本的其余信息,最初存储到数据库。
残缺代码详见 https://github.com/libra146/learnscrapy/tree/spa5
spa6
spa6 阐明如下:
电影数据网站,数据通过 Ajax 加载,数据接口参数加密且有工夫限度,源码通过混同,适宜 JavaScript 逆向剖析。
通过剖析及测试,sp6 和 spa2 貌似是一样的接口,间接应用 spa2 的代码就能够获取到数据,所以就不反复写了。???????? 不晓得为什么会这样,目前来说我还没发现有什么区别,如果有晓得的小伙伴能够通知我。
总结
这六个网站相比上一篇文章难度略微高了一点,波及到了 Ajax 接口和 JS 逆向,能够借此学习到逆向相干常识和 JS 调试技巧。
参考链接
https://docs.scrapy.org/en/latest/index.html