刚过年,又要到了一年一季的毕业季,马上就要到了大四学长学姐们提交毕业论文的时节,这次爬《汽车之家》的文章就是帮一位学长准备毕业论文研究资料。汽车之家的反爬虫措施做得很好,用了字体反爬技术。对于这类的反爬技术,我上次在帮另一位学长爬《大众点评》的时候也遇见过,当时并没有认真研究是怎样对付这类技术的,现在又遇见了,所以说“学习的苦,一定要吃”。为了学习《汽车之家》的反爬技术,我几乎参考完了所有关于它的博客,最终完成了技能学习。
文章目录
1、汽车之家论坛
- 这里以比亚迪新能源汽车作为爬取对象
1.1、分析网页构造
- 我用的是谷歌浏览器,打开网页后,右击检查,我们发现有些字体并没有正常在源码中显示,如图所示:
这就是重点了,它的反爬虫技术真面目,让你拿不到完整的信息!
接下来爬取,,我们先爬取一部分网页源代码来看看它的真相
1.2、获取网页源代码
import requests
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
url = 'https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-1.html'
requests.get(url=url, headers=headers).text
结果分析: 上面...
不真是网页源码中隐藏住的字体吗?这是一种字体编码,你会发现,同一个字的编码是相同的。如何进行字体反爬我就不具体讲解了,具体内容可以参看这位博主的文章,也很感谢这位博主的文章给予帮助!
参考文章: https://blog.csdn.net/zwq912318834/article/details/80268149
如果你不喜欢给电脑安装软件的话,可以使用“百度字体编辑器”来查看它的字体编码
地址: http://fontstore.baidu.com/static/editor/index.html
1.3、用户随机代理
- 既然是要模拟客户端,不可能只用一个用户去获取大量的信息,使用随机代理,就可以减少被识别反爬的概率,如果有必要,还可以加上IP代理,就不深入探讨了。
from fake_useragent import UserAgent
#随机生成5个不同的浏览器代理
for i in range(5):
headers = {
"User-Agent" : UserAgent().chrome #chrome浏览器代理
# "User-Agent" : UserAgent().random #任意浏览器代理
}
print (headers)
输出结果:
{'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'}
{'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36'}
{'User-Agent': 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36'}
{'User-Agent': 'Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36'}
{'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36'}
1.4、字体替换
# -*- coding:utf-8 -*-
import requests
from lxml import html
import re
from fontTools.ttLib import TTFont
# 定义字体文件的名字
fontFileName = "autohomeFont.ttf"
headerInfo = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
'host':'club.autohome.com.cn',
}
# 爬取链接
url = "https://club.autohome.com.cn/bbs/thread/6e20dd257e96ce65/84283887-1.html"#评论+回复
# 获取页面源代码
resp = requests.get(url, headers = headerInfo)
# 用正则表达式提取ttf字体文件的地址
# url('//k3.autoimg.cn/g24/M06/3F/FF/wKgHH1qWFoGATQa3AABhmFKVVrQ43..ttf') format('woff');}
ttfUrlRe = re.search(",url\('(//.*.ttf)'\) format\('woff'\)", resp.text, re.DOTALL)
ttfUrl = ""
if ttfUrlRe:
ttfUrl = "https:" + ttfUrlRe.group(1)
if ttfUrl:
# 以文件流的方式,抓取ttf字体文件
ttfFileStream = requests.get(ttfUrl, stream = True)
# 将数据流保存在本地的ttf文件中(新创建)
with open(fontFileName, "wb") as fp:
for chunk in ttfFileStream.iter_content(chunk_size=1024):
if chunk:
fp.write(chunk)
# 用fontTools模块解析字体库文件
fontObject = TTFont(fontFileName)
# 按顺序拿到各个字符的unicode编码
# ['.notdef', 'uniED8F', 'uniED3D', …… ]
uniWordList = fontObject['cmap'].tables[0].ttFont.getGlyphOrder()
# print(f"自定义字体列表(unicorn编码): {uniWordList}")
# 将各个字符的unicode编码转换成utf-8编码
# [b'\xee\xb6\x8f', b'\xee\xb4\xbd', b'\xee\xb7\xb1', …… ]
utf8WordList = [eval("u'\\u" + uniWord[3:] + "'").encode("utf-8") for uniWord in uniWordList[1:]]
# print(f"自定义字体列表( utf-8 编码): {utf8WordList}")
# 获取发帖内容文字
response = html.fromstring(resp.text)
contentLst = response.xpath("//div[@class='tz-paragraph']//text()")
descriptions = response.xpath("//div[@class='description']//text()")
comments = response.xpath('//*[@id="maxwrap-reply"]//div[@class="w740"]/text()')
replys = response.xpath('//*[@id="maxwrap-reply"]//div[@class="w740"]/div[last()]/text()')
# 这个部分的逻辑需要特别注意,因为自定义字体,也就是隐藏字符是以utf-8的形式存在的
# 所有一开始,我们就要以utf-8的编码形式来保持文本内容
content = ''.encode("utf-8")
for elem in contentLst:
content += elem.encode("utf-8")
# 对图片的描述
description = ''.encode("utf-8")
for elem in descriptions:
description += elem.encode("utf-8")
#评论
comment = ''.encode("utf-8")
for elem in comments:
comment += elem.encode("utf-8")
# 回复
reply = ''.encode("utf-8")
for elem in replys:
reply += elem.encode("utf-8")
# 录入字体文件中的字符。必须要以国际标准的unicode编码,取代汽车之家自己定义的字体编码
# 这个部分目前是手动输入,但是多次请求,每次拿到的ttf文件可能都不一样,甚至同一个字形,在不同的ttf文件中编码也不同,这个部分需要尤其注意
# 因为是python3,所以这些字符直接就是Unicode编码
wordList = ['一', '七', '三', '上', '下', '不', '九', '了', '二', '五', '低', '八',
'六', '十', '的', '着', '近', '远', '长', '右', '呢', '和', '四', '地', '坏',
'多', '大', '好', '小', '少', '短', '矮', '高', '左', '很', '得', '是', '更',
]
# print(f"字体文件中字形列表: {wordList}")
print(f"contentBefort = {content.decode('utf-8')}")
print('--------------- After Convert -----------------')
# 因为之前提到过,在网页源代码中,这种“” 特殊字符是utf-8编码,所以我们要以utf-8的模式去进行查找替换
# content 是字符串,是Unicode编码
for i in range(len(utf8WordList)):
# 将自定的字体信息,替换成国际标准
content = content.replace(utf8WordList[i], wordList[i].encode('utf-8'))
description = description.replace(utf8WordList[i], wordList[i].encode('utf-8'))
comment = comment.replace(utf8WordList[i], wordList[i].encode('utf-8'))
reply = reply.replace(utf8WordList[i], wordList[i].encode('utf-8'))
content = str(content.decode('utf-8'))
description = str(description.decode('utf-8'))
comment = str(comment.decode('utf-8')).replace("\r\n","").replace(" ","")
reply = str(reply.decode('utf-8')).replace("\r\n","")
print("content = ",content)
print("description = ",description)
print("comment = ",comment)
print("reply = ",reply)
替换结果:
1.5、爬取论坛链接主题链接
1.5.1、构造论坛首页翻页链接
- 如何看一个网页的链接是怎么构造的,我们可以通过点击下一页,并多复制几个链接来进行对比,或者改变一些参数来进行尝试。
- 论坛1~8页的链接:
1 https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=1&entry=44
2 https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=2
3 https://sou.autohome.com.cn/luntan?entry=44&page=3&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4
4 https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=4&error=0
5 https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=5&entry=44
6 https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=6
7 https://sou.autohome.com.cn/luntan?entry=44&page=7&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4
8 https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=8&error=0
- 可以很容易的分析出来,它的URL相邻的四个都不一样,每四个就要重复一次格式,那就好办了。
for i in range(1, 52):
if i%4 == 1:
index_url = "https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=" + str(i) + "&entry=44"
elif i%4 == 2:
index_url = "https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=" +str(i)
elif i%4 == 3:
index_url = "https://sou.autohome.com.cn/luntan?entry=44&page=" + str(i) + "&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4"
else:
index_url = "https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=" + str(i) + "&error=0"
print ("%s: %s"%(i,index_url))
生成结果:
1: https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=1&entry=44
2: https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=2
3: https://sou.autohome.com.cn/luntan?entry=44&page=3&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4
4: https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=4&error=0
5: https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=5&entry=44
6: https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=6
7: https://sou.autohome.com.cn/luntan?entry=44&page=7&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4
8: https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=8&error=0
9: https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=9&entry=44
10: https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=10
11: https://sou.autohome.com.cn/luntan?entry=44&page=11&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4
12: https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=12&error=0
结论: 其实,所有链接都是一样的格式,它也是可以正常访问服务器的,只是,爬虫模拟客户端时,尽量把它模仿到位,减少被反爬的概率。
1.5.2、爬取论点链接
- 论坛的主页几乎没有什么反爬措施,拿到链接是相当容易的,我直接贴代码了
from lxml import etree
import requests
from fake_useragent import UserAgent
#2019年起一共有51个页面
index = "https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0"
headers = {
"User-Agent" : UserAgent().chrome#使用随机代理
}
html = requests.get(url=index, headers=headers).text
etree = etree.HTML(html)
for k in etree.xpath('//*[@id="content"]/div[1]/div[2]/div/dl'):
link = k.xpath('./dt/a/@href')[0]
print (link)
爬取结果:
http://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-1.html
http://club.autohome.com.cn/bbs/thread/b4fbeb293d318cc2/84431220-1.html
http://club.autohome.com.cn/bbs/thread/b091dc1bafae9e96/84409274-1.html
http://club.autohome.com.cn/bbs/thread/6e20dd257e96ce65/84283887-1.html
http://club.autohome.com.cn/bbs/thread/69c3430a83c283f9/84155617-1.html
http://club.autohome.com.cn/bbs/thread/298e255cc86981e1/84001022-1.html
http://club.autohome.com.cn/bbs/thread/f244b3672b1cb9be/83908505-1.html
http://club.autohome.com.cn/bbs/thread/221a469e9ce0b1e6/83480757-1.html
http://club.autohome.com.cn/bbs/thread/49f989239d788ab8/83341349-1.html
1.6、实现评论内容翻页
-
有些文章的评论有很多的页面,每一个页面的链接都需要爬取到,但是它往往又不是完整的,可能会隐藏点中间部分的链接,如下图所示:
-
对于这样的情况,选择构造链接是一个不错的选择
-
思路:
- 获取源代码
- 提取页数最大值
- 判断:只有一个链接时,就是原链接;第二个链接通过原链接的 “-” 切分,加上当前页数值,再拼接上“.html”
import requests
import re
from lxml import html
from fake_useragent import UserAgent
content_url = "https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-1.html"
headers = {
"User-Agent" : UserAgent().chrome#使用随机代理
}
resp = requests.get(url=content_url, headers=headers)
response = html.fromstring(resp.text)
maxPages = response.xpath("//span[@class='fs']/text()")[0]#找到评论的页数
maxPage = re.sub(r'\D', "", maxPages)#提取数字
maxPage = int(maxPage)
for page in range(1, maxPage+1):
if page == 1:
print ("这篇文章共有%s页,正在爬取第1页"%(maxPage))
print (content_url)
pass
else:
content_url = content_url.split('-')[0] + "-" + str(page) + ".html"#自己构造链接
print ("这篇文章共有%s页,正在爬取第%s页"%(maxPage, page))
print (content_url)
运行结果:
这篇文章共有21页,正在爬取第1页
https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-1.html
这篇文章共有21页,正在爬取第2页
https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-2.html
这篇文章共有21页,正在爬取第3页
https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-3.html
这篇文章共有21页,正在爬取第4页
https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-4.html
这篇文章共有21页,正在爬取第5页
https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-5.html
这篇文章共有21页,正在爬取第6页
https://club.autohome.com.cn/bbs/thread/964c33457ece138a/84139333-6.html
......
1.7、大功告成,附上源码
from lxml import etree
from lxml import html
import requests,re,time
from fake_useragent import UserAgent
from fontTools.ttLib import TTFont
import random
startTime =time.time()#获取开始时的时间
# 定义字体文件的名字
fontFileName = "autohomeFont.ttf"
fileName = "比亚迪新能源论坛.txt"
forum_urls = []#论坛的每一个话题的链接
def forum_url_spider():
#论坛一共有51个页面在2019-2020/2/6
for i in range(1, 10):#1:52,分开采集,避免反爬
if i%4 == 1:
index_url = "https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=" + str(i) + "&entry=44"
elif i%4 == 2:
index_url = "https://sou.autohome.com.cn/luntan?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=44&error=0&page=" +str(i)
elif i%4 == 3:
index_url = "https://sou.autohome.com.cn/luntan?entry=44&page=" + str(i) + "&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4"
else:
index_url = "https://sou.autohome.com.cn/luntan?entry=44&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=" + str(i) + "&error=0"
# print ("正在获取论坛首页第%s页链接,一共有51个页面需要爬取"%i)
print (index_url)
headers = {
"User-Agent" : UserAgent().chrome,#使用随机代理
}
time.sleep(random.randint(1,5))
html = requests.get(url=index_url, headers=headers).text
etrees = etree.HTML(html)
for k in etrees.xpath('//*[@id="content"]/div[1]/div[2]/div/dl'):
link = k.xpath('./dt/a/@href')[0]
forum_urls.append(link)
# 检查该话题有多少页
def ckeck_max_page():
for content_url in forum_urls:
headers = {
"User-Agent" : UserAgent().chrome#使用随机代理
}
print (content_url)
time.sleep(random.randint(1,6))
resp_html = requests.get(url=content_url, headers=headers)
response = html.fromstring(resp_html.text)
maxPages = response.xpath("//span[@class='fs']/text()")[0]
maxPage = re.sub(r'\D', "", maxPages)#提取数字
maxPage = int(maxPage)
for page in range(1, maxPage+1):
if page == 1:
try:
print ("这篇文章共有%s页,正在爬取第1页"%(maxPage))
print (content_url)
title = response.xpath("//div[@class='maxtitle']/text()")[0]
with open (fileName, 'a', encoding='utf-8') as f:
f.write(title)
f.write("\n")
f.close()
print ("title = ",title)
download_font(resp_html)
content_spider(resp_html)
except:
pass
else:
content_url = content_url.split('-')[0] + "-" + str(page) + ".html"
print ("这篇文章共有%s页,正在爬取第%s页"%(maxPage, page))
print (content_url)
time.sleep(random.randint(1,6))
resp_html = requests.get(url=content_url, headers=headers)
download_font(resp_html)
content_spider(resp_html)
def download_font(resp_html):
# 用正则表达式提取ttf字体文件的地址
# url('//k3.autoimg.cn/g24/M06/3F/FF/wKgHH1qWFoGATQa3AABhmFKVVrQ43..ttf') format('woff');}
ttfUrlRe = re.search(",url\('(//.*.ttf)'\) format\('woff'\)", resp_html.text, re.DOTALL)
ttfUrl = ""
if ttfUrlRe:
ttfUrl = "https:" + ttfUrlRe.group(1)
if ttfUrl:
# 以文件流的方式,抓取ttf字体文件
ttfFileStream = requests.get(ttfUrl, stream = True)
# 将数据流保存在本地的ttf文件中(新创建)
with open(fontFileName, "wb") as fp:
for chunk in ttfFileStream.iter_content(chunk_size=1024):
if chunk:
fp.write(chunk)
def content_spider(resp_html):
# 用fontTools模块解析字体库文件
fontObject = TTFont(fontFileName)
# 按顺序拿到各个字符的unicode编码
# ['.notdef', 'uniED8F', 'uniED3D', …… ]
uniWordList = fontObject['cmap'].tables[0].ttFont.getGlyphOrder()
# print(f"自定义字体列表(unicorn编码): {uniWordList}")
# 将各个字符的unicode编码转换成utf-8编码
# [b'\xee\xb6\x8f', b'\xee\xb4\xbd', b'\xee\xb7\xb1', …… ]
utf8WordList = [eval("u'\\u" + uniWord[3:] + "'").encode("utf-8") for uniWord in uniWordList[1:]]
# print(f"自定义字体列表( utf-8 编码): {utf8WordList}")
# 获取发帖内容文字
response = html.fromstring(resp_html.text)
contentLst = response.xpath("//div[@class='tz-paragraph']//text()")
descriptions = response.xpath("//div[@class='description']//text()")
comments = response.xpath('//*[@id="maxwrap-reply"]//div[@class="w740"]/text()')
replys = response.xpath('//*[@id="maxwrap-reply"]//div[@class="w740"]/div[last()]/text()')
# 这个部分的逻辑需要特别注意,因为自定义字体,也就是隐藏字符是以utf-8的形式存在的
# 所有一开始,我们就要以utf-8的编码形式来保持文本内容
content = ''.encode("utf-8")
for elem in contentLst:
content += elem.encode("utf-8")
# 对图片的描述
description = ''.encode("utf-8")
for elem in descriptions:
description += elem.encode("utf-8")
#评论
comment = ''.encode("utf-8")
for elem in comments:
comment += elem.encode("utf-8")
# 回复
reply = ''.encode("utf-8")
for elem in replys:
reply += elem.encode("utf-8")
# 录入字体文件中的字符。必须要以国际标准的unicode编码,取代汽车之家自己定义的字体编码
# 这个部分目前是手动输入,但是多次请求,每次拿到的ttf文件可能都不一样,甚至同一个字形,在不同的ttf文件中编码也不同,这个部分需要尤其注意
# 因为是python3,所以这些字符直接就是Unicode编码
wordList = ['一', '七', '三', '上', '下', '不', '九', '了', '二', '五', '低', '八',
'六', '十', '的', '着', '近', '远', '长', '右', '呢', '和', '四', '地', '坏',
'多', '大', '好', '小', '少', '短', '矮', '高', '左', '很', '得', '是', '更',
]
# 因为之前提到过,在网页源代码中,这种“” 特殊字符是utf-8编码,所以我们要以utf-8的模式去进行查找替换
# content 是字符串,是Unicode编码
for i in range(len(utf8WordList)):
# 将自定的字体信息,替换成国际标准
content = content.replace(utf8WordList[i], wordList[i].encode('utf-8'))
description = description.replace(utf8WordList[i], wordList[i].encode('utf-8'))
comment = comment.replace(utf8WordList[i], wordList[i].encode('utf-8'))
reply = reply.replace(utf8WordList[i], wordList[i].encode('utf-8'))
content = str(content.decode('utf-8'))
description = str(description.decode('utf-8'))
comment = str(comment.decode('utf-8')).replace("\r\n","").replace(" ","")
reply = str(reply.decode('utf-8')).replace("\r\n","")
with open (fileName, 'a', encoding='utf-8') as f:
f.write(content)
f.write(description)
f.write(comment)
f.write(reply)
f.write("\n")
f.close()
if __name__ == '__main__':
forum_url_spider()
ckeck_max_page()
endTime =time.time()#获取结束时的时间
useTime =(endTime-startTime)/60
print ("该次所获的信息一共使用%s分钟"%useTime)
2、汽车之家问答
- 问答部分和论坛部分的方式是一样的,就不再重述了,如果不明白的地方,可以看看论坛部分。
from lxml import etree
from lxml import html
import requests,re,time
from fake_useragent import UserAgent
from fontTools.ttLib import TTFont
import random
startTime =time.time()#获取开始时的时间
# 定义字体文件的名字
fontFileName = "autohomeFont.ttf"
fileName = "比亚迪新能源问答.txt"
forum_urls = []#论坛的每一个话题的链接
def forum_url_spider():
#问答一共有8个页面
for i in range(1, 9):
if i%4 == 1:
index_url = "https://sou.autohome.com.cn/zhidao?entry=90&page=" + str(i) + "&error=0&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4"
elif i%4 == 2:
index_url = "https://sou.autohome.com.cn/zhidao?entry=90&q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&page=" + str(i) + "&error=0"
elif i%4 == 3:
index_url = "https://sou.autohome.com.cn/zhidao?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&error=0&page=" + str(i) + "entry=90"
else:
index_url = "https://sou.autohome.com.cn/zhidao?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&entry=90&error=0&page=" + str(i)
print (index_url)
headers = {
"User-Agent" : UserAgent().chrome #使用随机代理
}
time.sleep(random.randint(1,5))
html = requests.get(url=index_url, headers=headers).text
etrees = etree.HTML(html)
for k in etrees.xpath('//*[@id="content"]/div[1]/div[2]/div/dl'):
link = k.xpath('./dt/a/@href')[0]
print (link)
forum_urls.append(link)
# 检查该话题有多少页
def ckeck_max_page():
for content_url in forum_urls:
headers = {
"User-Agent" : UserAgent().chrome #使用随机代理
}
print (content_url)
time.sleep(random.randint(1,4))
resp_html = requests.get(url=content_url, headers=headers)
response = html.fromstring(resp_html.text)
maxPages = response.xpath("//span[@class='fs']/text()")[0]
maxPage = re.sub(r'\D', "", maxPages)#提取数字
maxPage = int(maxPage)
for page in range(1, maxPage+1):
if page == 1:
print ("这篇文章共有%s页,正在爬取第1页"%(maxPage))
print (content_url)
title = response.xpath("//div[@class='qa-maxtitle']/text()")[0]
with open (fileName, 'a', encoding='utf-8') as f:
f.write(title)
f.write("\n")
f.close()
print ("title = ",title)
download_font(resp_html)
content_spider(resp_html)
else:
content_url = content_url.split('-')[0] + "-" + str(page) + ".html"
print ("这篇文章共有%s页,正在爬取第%s页"%(maxPage, page))
print (content_url)
time.sleep(random.randint(1,4))
resp_html = requests.get(url=content_url, headers=headers)
download_font(resp_html)
content_spider(resp_html)
def download_font(resp_html):
# 用正则表达式提取ttf字体文件的地址
# url('//k3.autoimg.cn/g24/M06/3F/FF/wKgHH1qWFoGATQa3AABhmFKVVrQ43..ttf') format('woff');}
ttfUrlRe = re.search(",url\('(//.*.ttf)'\) format\('woff'\)", resp_html.text, re.DOTALL)
ttfUrl = ""
if ttfUrlRe:
ttfUrl = "https:" + ttfUrlRe.group(1)
if ttfUrl:
# 以文件流的方式,抓取ttf字体文件
ttfFileStream = requests.get(ttfUrl, stream = True)
# 将数据流保存在本地的ttf文件中(新创建)
with open(fontFileName, "wb") as fp:
for chunk in ttfFileStream.iter_content(chunk_size=1024):
if chunk:
fp.write(chunk)
def content_spider(resp_html):
# 用fontTools模块解析字体库文件
fontObject = TTFont(fontFileName)
# 按顺序拿到各个字符的unicode编码
# ['.notdef', 'uniED8F', 'uniED3D', …… ]
uniWordList = fontObject['cmap'].tables[0].ttFont.getGlyphOrder()
# print(f"自定义字体列表(unicorn编码): {uniWordList}")
# 将各个字符的unicode编码转换成utf-8编码
# [b'\xee\xb6\x8f', b'\xee\xb4\xbd', b'\xee\xb7\xb1', …… ]
utf8WordList = [eval("u'\\u" + uniWord[3:] + "'").encode("utf-8") for uniWord in uniWordList[1:]]
# print(f"自定义字体列表( utf-8 编码): {utf8WordList}")
# 获取发帖内容文字
response = html.fromstring(resp_html.text)
contentLst = response.xpath("//div[@class='w740']//text()")
# 这个部分的逻辑需要特别注意,因为自定义字体,也就是隐藏字符是以utf-8的形式存在的
# 所有一开始,我们就要以utf-8的编码形式来保持文本内容
content = ''.encode("utf-8")
for elem in contentLst:
content += elem.encode("utf-8")
# 录入字体文件中的字符。必须要以国际标准的unicode编码,取代汽车之家自己定义的字体编码
# 这个部分目前是手动输入,但是多次请求,每次拿到的ttf文件可能都不一样,甚至同一个字形,在不同的ttf文件中编码也不同,这个部分需要尤其注意
# 因为是python3,所以这些字符直接就是Unicode编码
wordList = ['一', '七', '三', '上', '下', '不', '九', '了', '二', '五', '低', '八',
'六', '十', '的', '着', '近', '远', '长', '右', '呢', '和', '四', '地', '坏',
'多', '大', '好', '小', '少', '短', '矮', '高', '左', '很', '得', '是', '更',
]
# 因为之前提到过,在网页源代码中,这种“” 特殊字符是utf-8编码,所以我们要以utf-8的模式去进行查找替换
# content 是字符串,是Unicode编码
for i in range(len(utf8WordList)):
# 将自定的字体信息,替换成国际标准
content = content.replace(utf8WordList[i], wordList[i].encode('utf-8'))
content = str(content.decode('utf-8')).replace("\r\n","").replace(" ","").replace(" ","\n")
with open (fileName, 'a', encoding='utf-8') as f:
f.write(content)
f.write("\n")
f.close()
if __name__ == '__main__':
forum_url_spider()
ckeck_max_page()
endTime =time.time()#获取结束时的时间
useTime =(endTime-startTime)/60
print ("该次所获的信息一共使用%s分钟"%useTime)
运行结果截图:
3、汽车之家新闻
3.1、新闻内容
from lxml import html
import requests,re,time
from fake_useragent import UserAgent
article_url = "https://www.autohome.com.cn/news/201909/945418.html"
headers = {
"User-Agent" : UserAgent().chrome
}
article_html = requests.get(url=article_url, headers=headers)
response = html.fromstring(article_html.text)
href = response.xpath('//*[@id="hudongreply"]//@href')
comment_url = "https:" + href[0] #获取到更多评论链接
print ("comment_url = ", comment_url)
#文章标题
article_details = response.xpath('//*[@id="articlewrap"]/h1/text()')[0].replace("\n","").replace(" ", "")
print ("article_title = ", article_details)
#文章内容
contentLst = response.xpath('//*[@id="articleContent"]//p//text()')
content = "".join(contentLst).replace("\u3000\u3000","").replace("\xa0","")
print ("content = ", content)
3.1、新闻评论
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
from lxml import html
import time
comment_url = "https://www.autohome.com.cn/comment/Articlecomment.aspx?articleid=945418#pvareaid=3311690"
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')#上面三行代码就是为了将Chrome不弹出界面,实现无界面爬取
driver = webdriver.Chrome(chrome_options=chrome_options)
option = webdriver.ChromeOptions()
option.add_argument('--proxy--server=112.84.55.122:9999')#使用代理IP
driver.get(comment_url)#打开网页网页
driver.implicitly_wait(6)#等待加载六秒
maxPage = driver.find_element_by_xpath('//*[@id="topPager"]/div/a[last()-1]').text
maxPage = int(maxPage)
for i in range(maxPage):
time.sleep(3)
source = driver.page_source
response = html.fromstring(source)
user_comment = response.xpath('//*[@id="reply-list"]/dd/p/text()')
user_comment = "".join(user_comment)
print (user_comment)
driver.find_element_by_xpath('//*[@id="topPager"]/div/a[last()]').click()
driver.quit()#推出并关闭浏览器
4、汽车之家文章
- 20190101~20200206共有文章约72页,每页10篇,约720篇。
注意: 汽车之家的文章包括之家原创的新闻中心 和 用户文章的车家号两个页组成。
- 上面已经讲解了对于新闻及其评论的爬取,现在只需要加上车家号就行了,由于车家号的车主文章评论太少,就不对它的评论进行爬取了。
4.1、爬取所有文章的链接
from lxml import etree
import requests
from fake_useragent import UserAgent
article_urls = [] #文章链接
for i in range(1, 5):
index_url = "https://sou.autohome.com.cn/wenzhang?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&searchtypeContent=content&sort=New&entry=43&class=0&error=0&page=" + str(i)
headers = {
"User-Agent" : UserAgent().chrome
}
index_html = requests.get(url=index_url, headers=headers).text
etrees = etree.HTML(index_html)
for k in etrees.xpath('//*[@id="content"]/div[1]/div[2]/div/dl'):
link = k.xpath('./dt/a/@href')[0]
print (link)
article_urls.append(link)
输出结果:
https://chejiahao.autohome.com.cn/info/5566800#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5564867#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5563583#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5563099#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5562095#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5560884#pvareaid=28086821202
http://www.autohome.com.cn/news/202002/968713.html
https://chejiahao.autohome.com.cn/info/5557390#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5557241#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5555108#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5516371#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5528244#pvareaid=28086821202
https://chejiahao.autohome.com.cn/info/5521957#pvareaid=28086821202
4.2、判断新闻和车家号
- 由于新闻和车家号的网页结构不一样,所以必须要分开爬取。它俩的链接参数也不一样,所以可以通过关键词来进行区别它们。
for article_url in article_urls:
if "news" in article_url:
print ("这是新闻页:", article_url)
if "info" in article_url:
print ("这是车家号:", article_url)
判断结果:
这是车家号: https://chejiahao.autohome.com.cn/info/5566800#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5564867#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5563583#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5563099#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5562095#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5560884#pvareaid=28086821202
这是新闻页: http://www.autohome.com.cn/news/202002/968713.html
这是车家号: https://chejiahao.autohome.com.cn/info/5557390#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5557241#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5555108#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5516371#pvareaid=28086821202
这是车家号: https://chejiahao.autohome.com.cn/info/5528244#pvareaid=28086821202
4.3、车家号文章
from lxml import html
import requests,re,time
from fake_useragent import UserAgent
article_url = "https://chejiahao.autohome.com.cn/info/5566800#pvareaid=28086821202"
headers = {
"User-Agent" : UserAgent().chrome
}
article_html = requests.get(url=article_url, headers=headers)
response = html.fromstring(article_html.text)
contentLst = response.xpath('//div[@class="introduce_content"]//text()')
content = "".join(contentLst).replace("\n","").replace(" ", "")
if len(contentLst) == 0:
contentLst = response.xpath('//p[@class="text"]//text()')
content = "".join(contentLst).replace("\n","").replace(" ", "")
print ("content = ", content)
4.4、代码汇总
from lxml import etree
from lxml import html
import requests,re,time
from fake_useragent import UserAgent
import random
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
startTime =time.time()#获取开始时的时间
fileName = "比亚迪新能源文章.txt"
article_urls = [] #文章链接
for i in range(1, 10):
index_url = "https://sou.autohome.com.cn/wenzhang?q=%b1%c8%d1%c7%b5%cf%d0%c2%c4%dc%d4%b4&searchtypeContent=content&sort=New&entry=43&class=0&error=0&page=" + str(i)
headers = {
"User-Agent" : UserAgent().chrome
}
index_html = requests.get(url=index_url, headers=headers).text
etrees = etree.HTML(index_html)
for k in etrees.xpath('//*[@id="content"]/div[1]/div[2]/div/dl'):
link = k.xpath('./dt/a/@href')[0]
article_urls.append(link)
for count,article_url in enumerate(article_urls):
try:
print ("正在爬取第%s个,一共有%s个"%(count+1, len(article_urls)))
headers = {
"User-Agent" : UserAgent().chrome
}
article_html = requests.get(url=article_url, headers=headers)
response = html.fromstring(article_html.text)
#之家原创评论
if "news" in article_url:
href = response.xpath('//*[@id="hudongreply"]//@href')
comment_url = "https:" + href[0]
#文章标题
article_details = response.xpath('//*[@id="articlewrap"]/h1/text()')[0].replace("\n","").replace(" ", "")
with open(fileName, "a", encoding="utf-8") as f:
f.write(article_details)
f.close()
print ("article_details = ", article_details)
contentLst = response.xpath('//*[@id="articleContent"]//p//text()')
content = "".join(contentLst).replace("\u3000\u3000","").replace("\xa0","")
with open(fileName, "a", encoding="utf-8") as f:
f.write(content)
f.close()
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')#上面三行代码就是为了将Chrome不弹出界面,实现无界面爬取
driver = webdriver.Chrome(chrome_options=chrome_options)
option = webdriver.ChromeOptions()
option.add_argument('--proxy--server=112.84.55.122:9999')#使用代理IP
driver.get(comment_url)#打开网页网页
driver.implicitly_wait(6)#等待加载六秒
maxPage = driver.find_element_by_xpath('//*[@id="topPager"]/div/a[last()-1]').text
maxPage = int(maxPage)
for i in range(maxPage):
time.sleep(2)
source = driver.page_source
response = html.fromstring(source)
user_comment = response.xpath('//*[@id="reply-list"]/dd/p/text()')
user_comment = "".join(user_comment)
with open(fileName, "a", encoding="utf-8") as f:
f.write(content)
f.close()
driver.find_element_by_xpath('//*[@id="topPager"]/div/a[last()]').click()
driver.quit()#推出并关闭浏览器
# 车友文章
if "info" in article_url:
contentLst = response.xpath('//div[@class="introduce_content"]//text()')
content = "".join(contentLst).replace("\n","").replace(" ", "")
if len(contentLst) == 0:
contentLst = response.xpath('//p[@class="text"]//text()')
content = "".join(contentLst).replace("\n","").replace(" ", "")
with open(fileName, "a", encoding="utf-8") as f:
f.write(content)
f.close()
except:
pass
endTime =time.time()#获取结束时的时间
useTime =(endTime-startTime)/60
print ("该次所获的信息一共使用%s分钟"%useTime)
程序分开进行:
运行结果:
- 上面这段程序是我在晚上4点钟运行的,同时分为7个程序爬取,虽然是手机wifi,约7分钟爬完720篇文章。
5、结果汇总截图
倡议: 我们快速爬取到自己想要的是一个方面,但是不建议大家在目标网站访问量大的时候去批量爬取别人网站,很容易给别人的服务器造成压力。