《汽车之家》字体反爬之论坛、问答、文章(新闻、车家号)及其评论爬取

刚过年,又要到了一年一季的毕业季,马上就要到了大四学长学姐们提交毕业论文的时节,这次爬《汽车之家》的文章就是帮一位学长准备毕业论文研究资料。汽车之家的反爬虫措施做得很好,用了字体反爬技术。对于这类的反爬技术,我上次在帮另一位学长爬《大众点评》的时候也遇见过,当时并没有认真研究是怎样对付这类技术的,现在又遇见了,所以说“学习的苦,一定要吃”。为了学习《汽车之家》的反爬技术,我几乎参考完了所有关于它的博客,最终完成了技能学习。

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

在这里插入图片描述
结果分析: 上面&#xe...不真是网页源码中隐藏住的字体吗?这是一种字体编码,你会发现,同一个字的编码是相同的。如何进行字体反爬我就不具体讲解了,具体内容可以参看这位博主的文章,也很感谢这位博主的文章给予帮助!
参考文章: 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、实现评论内容翻页

  • 有些文章的评论有很多的页面,每一个页面的链接都需要爬取到,但是它往往又不是完整的,可能会隐藏点中间部分的链接,如下图所示:
    在这里插入图片描述

  • 对于这样的情况,选择构造链接是一个不错的选择

  • 思路:

  1. 获取源代码
  2. 提取页数最大值
  3. 判断:只有一个链接时,就是原链接;第二个链接通过原链接的 “-” 切分,加上当前页数值,再拼接上“.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、结果汇总截图

在这里插入图片描述

倡议: 我们快速爬取到自己想要的是一个方面,但是不建议大家在目标网站访问量大的时候去批量爬取别人网站,很容易给别人的服务器造成压力。

发布了84 篇原创文章 · 获赞 64 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ayouleyang/article/details/104208616
今日推荐