一、准备阶段
1.目标网站观察
①采集网页属于静态网页
②网站做了字体反爬,关键信息在html中是乱码
二、关于起点中文网的字体反爬
1.什么是字体反爬
① 网站采取的一种反爬措施
② 通过自定义字体文件的方式,让前端显示正常,但在html中是乱码
③ 一般自定义的字体文件是随机的,每次请求都会改变。还有一种字体反爬更加变态,在字体文件中还有一次随机,这样子即使获取到字体文件也很难找到字体Unicode与前端显示本文的正确对应关系,这种程度的反爬,我想应该需要通过机器学习来解决,本文并没有对此作答。
2.对起点中文网的字体反爬的解析
①通过对整个html的分析,找到了下载woff字体文件的网址,截取出最重要的部分
②其中code对应的就是html页面中乱码的十六进制编码,name对应的应该就是数字的英文表达
③看到这里,思路其实就应该清晰了。但这应该算是最简单的字体反爬了,像58同城,还需要再找一组对应,最后结果应该是GlyphOrder标签中的id减去1;猫眼电影应该是最难的,属于我上述的双随机,可能需要KNN算法来解决。
三、代码逻辑
1.通过请求起始页面采集基本信息以及详细页面的url
2.在详细页面中利用re正则表达式解析得到字体下载文件和乱码部分
3.利用fontTools.ttLib解析字体文件得到对应camp
4.配合乱码部分的十六进制编码,得到正确结果
5.scrapy crawl main -o res.csv将结果保存为csv
四、代码部分
main.py
import scrapy
import re
import requests
from fontTools.ttLib import TTFont
from io import BytesIO
import time
from ..items import QidianxiaoshuoItem
class MainSpider(scrapy.Spider):
name = 'main'
# allowed_domains = ['main.com']
# start_urls = ['https://www.qidian.com/rank/readIndex?page=1',
# 'https://www.qidian.com/rank/readIndex?page=2']
start_urls = [f'https://www.qidian.com/rank/readIndex?page={i}' for i in range(1,6)]
def get_font(self,url):
time.sleep(1)
response = requests.get(url)
font = TTFont(BytesIO(response.content))
cmap = font.getBestCmap()
font.close()
return cmap
def get_encode(self,cmap, values):
WORD_MAP = {'zero': '0', 'one': '1', 'two': '2', 'three': '3', 'four': '4', 'five': '5', 'six': '6',
'seven': '7',
'eight': '8', 'nine': '9', 'period': '.'}
word_count = ''
list = values.split(';')
list.pop(-1)
for value in list:
value = value[2:]
key = cmap[int(value)]
word_count += WORD_MAP[key]
return word_count
def get_nums(self,url):
# 获取当前页面的html
time.sleep(1)
response = requests.get(url).text
pattern = re.compile('</style><span.*?>(.*?)</span>', re.S)
# 获取当前页面所有被字数字符
numberlist = re.findall(pattern, response)
# 获取当前包含字体文件链接的文本
reg = re.compile('<style>(.*?)\s*</style>', re.S)
fonturl = re.findall(reg, response)[0]
# 通过正则获取当前页面字体文件链接
url = re.search('woff.*?url.*?\'(.+?)\'.*?truetype', fonturl).group(1)
cmap = self.get_font(url)
print('cmap:', cmap)
num_list = []
for a in numberlist:
num_list.append(self.get_encode(cmap, a))
return num_list
def parse(self, response):
res = response.xpath('//*[@id="rank-view-list"]/div/ul/li')
for i in res:
url = i.css('div:nth-child(1) a::attr(href)').extract_first()
url = 'https:' + url
yield scrapy.Request(url,callback = self.parse_one,meta={'url':url})
def parse_one(self,response):
book_name = response.css('div.book-info h1 em::text').extract_first()
author = response.css('a.writer::text').extract_first()
intro = response.xpath('/html/body/div/div[6]/div[1]/div[2]/p[2]/text()').extract_first()
num_list = self.get_nums(response.meta['url'])
del num_list[1]
word_num = str(num_list[0]) + response.xpath('/html/body/div/div[6]/div[1]/div[2]/p[3]/cite[1]/text()').extract_first()
recommend_all = str(num_list[1]) + response.xpath('/html/body/div/div[6]/div[1]/div[2]/p[3]/cite[2]/text()').extract_first()
recommend_week = str(num_list[2]) + response.xpath('/html/body/div/div[6]/div[1]/div[2]/p[3]/cite[3]/text()').extract_first()
item = QidianxiaoshuoItem()
item['book_name'] = book_name
item['author'] = author
item['intro'] = intro
item['word_num'] = word_num
item['recommend_all'] = recommend_all
item['recommend_week'] = recommend_week
yield item
items.py
import scrapy
class QidianxiaoshuoItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
book_name = scrapy.Field()
author = scrapy.Field()
intro = scrapy.Field()
word_num = scrapy.Field()
recommend_all = scrapy.Field()
recommend_week = scrapy.Field()
最终结果:
其中一个页面:
CSV文件:
五、总结
遇见字体反爬的网页,需要更多的耐心,要在一张张网页源码中找到需要的信息(woff文件下载网址,乱码部分)。