从零实现百度指数爬虫~

作者: Big_Monster | 来源:发表于2018-08-12 00:47 被阅读4次

    想要爬取一个网站,肯定需要先对该网站的规则有所了解~
    现在开始看看百度指数的网站采用了什么渲染,以便我们对症下药才能拿到我们想要的数据~

    • 打开百度指数
    • 输入关键词 我这里输入的是韩国旅游
      在这里发现必须要登录才能进行下一步,那么就输入帐号密码进行登录
    • 选择相应的日期进行获取数据
    • 在渲染框上移动显示相应日期的数据

    这个便是我们人为查看百度指数的一步一步操作

    通过查看网站的html源代码可以看到每个指数都是由css拼接而成,已经不能直接获取数据了。
    那么再查看网络的发包情况,发包情况复杂-,- 就没有深入研究了。
    那么还是跟着前人的脚步使用网页截图识别数据吧~

    在上面已经列出了我们人为操作获取的几个步骤,在其他方法复,或者难度较高的情况下。那么我们就可以使用selenium模拟浏览器操作获取到我们想要的数据。

    在这里我使用的是firefox作为selenium操作的浏览器,很多人都习惯用chrome,但是我本身使用了挺久的firefox,懒得弄这些书签,懒得在电脑里面装两个浏览器,就直接使用firefox了。。

    from selenium import webdriver
    firefox = webdriver.Firefox()
    firefox.get("http://www.baidu.com") #打开百度首页
    

    因为之前在百度指数页面登录的时候,selenium老是出现各种奇怪的错误。。也不知道是我代码写错的问题还是因为我的firefox的驱动版本太高。。
    之后我就使用在百度首页登录之后,再打开百度指数页面就没有这些错误信息了~
    ps: 推荐使用你个人使用的浏览器配置进行模拟操作,之前我没有使用这个配置的时候,百度老是要发送验证码验证登录,严重拖慢了登录的速度。。。换成个人使用的浏览器配置之后就不需要验证码了,也不知道是怎么回事,希望懂的大佬能给我解答一下。。

    profileDir = 'C:/Users/monster/AppData/Roaming/Mozilla/Firefox/Profiles/zi1rtobp.default'
    profile = webdriver.FirefoxProfile(profileDir)
    firefox = webdriver.Firefox(profile)
    

    打开百度指数页面之前的操作步骤

    • 打开百度首页的时候开始进行登录操作
    • 登录成功之后打开百度指数页面

    百度首页.jpg

    点击登录按钮

    firefox.find_element_by_xpath('/html/body/div[1]/div[1]/div/div[3]/a[7]').click()
    

    二维码界面.jpg

    点击 用户名登录

    firefox.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__footerULoginBtn"]').click()
    

    登录界面.jpg

    输入用户名与密码并点击登录

    firefox.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__userName"]').clear()
    firefox.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__userName"]').send_keys('your username')
    firefox.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__password"]').clear()
    firefox.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__password"]').send_keys('your password')
    firefox.find_element_by_xpath('//*[@id="TANGRAM__PSP_10__submit"]').click()
    

    登录成功之后该打开新页面:百度指数并切换到新的标签页

    js = "window.open('http://index.baidu.com/?tpl=trend&word=%BA%AB%B9%FA%C2%C3%D3%CE')"
    firefox.execute_script(js)#这里我为了方便,就直接输入该关键词的url了,免去了百度指数页面的点击步骤
    handles = firefox.window_handles
    for handle in handles:# 切换窗口
        if handle != firefox.current_window_handle:
              firefox.close() # 关闭第一个窗口
              firefox.switch_to.window(handle) #切换到第二个窗口
    

    到达新页面的时候就开始进行选择日期操作了~

    firefox.maximize_window()
    firefox.find_elements_by_xpath("//div[@class='box-toolbar']/a")[6].click()
    firefox.find_elements_by_xpath("//span[@class='selectA yearA']")[0].click()
    firefox.find_element_by_xpath("//span[@class='selectA yearA slided']//div//a[@href='#%s']" % str(year)).click()
    firefox.find_elements_by_xpath("//span[@class='selectA monthA']")[0].click()
    firefox.find_element_by_xpath("//span[@class='selectA monthA slided']//ul//li//a[@href='#%s']" % str(month)).click()
    # 选择网页上的截止日期
    firefox.find_elements_by_xpath("//span[@class='selectA yearA']")[1].click()
    firefox.find_element_by_xpath("//span[@class='selectA yearA slided']//div//a[@href='#%s']" % str(year)).click()
    firefox.find_elements_by_xpath("//span[@class='selectA monthA']")[1].click()
    firefox.find_element_by_xpath("//span[@class='selectA monthA slided']//ul//li//a[@href='#%s']" % str(month)).click()
    firefox.find_element_by_xpath("//input[@value='确定']").click()
    

    接下来我们要模拟鼠标在渲染框上的每次移动~

    #获取数据框的属性,截取网页截图中的数据框
    def find_real_span(file_name):
        try:
            tag = firefox.find_element_by_xpath('//div[@id="viewbox"]')
            print(tag.location)
            print(tag.size)
            left = tag.location['x']
            top = tag.location['y']
            right = left + tag.size['width']
            bottom = top + tag.size['height']
            im = Image.open(file_name)
            im = im.crop((left, top, right, bottom))
            im.save(file_name)
        except BaseException as e:
            print(e)
            print('get attrib error')
    
    file_name = path_dir + '/%s.png'
    xoyelement = firefox.find_elements_by_css_selector("#trend rect")[2]
    real_x = 20
    x_place = 1
    #先移动一次,发送请求获取数据,之后再移动到这个位置就数据就会更快渲染出来
    for i in range(self.move_time):
        ActionChains(firefox).move_to_element_with_offset(xoyelement,x_place, 20).perform()
        x_place += 40.4666
    x_place = 1
    #获取数据的鼠标操作
    for i in range(self.move_time):
        ActionChains(firefox).move_to_element_with_offset(xoyelement,x_place, 20).perform()
        time.sleep(2)
        if self.ex_box():
            time.sleep(2)
            firefox.save_screenshot(file_name % str(i))
            find_real_span(file_name % str(i))
        else:
    #数据框不出现的情况
            cout = 0
            while True:
                if cout >= 4:
                    break
                ActionChains(firefox).move_to_element_with_offset(xoyelement, x_place - 40.46666/2, 10 ).perform()
                for k in range(20):
                    if cout < 2 : 
                        ActionChains(firefox).move_to_element_with_offset(xoyelement, x_place - 40.46666/2 + k+1 , 5 ).perform()
                        if self.ex_box() and k >= 3:
                            time.sleep(1)
                            firefox.save_screenshot(file_name % str(i))
                            find_real_span(file_name % str(i))
                            cout = 4
                            break
                    else:
                        ActionChains(firefox).move_to_element_with_offset(xoyelement, x_place - 40.46666/2 - 5 + k+1 , 5 ).perform()
                        if self.ex_box() and k >= 8:
                            time.sleep(1)
                            firefox.save_screenshot(file_name % str(i))
                            find_real_span(file_name % str(i))
                            cout = 4
                            break
                cout += 1
    
        time.sleep(1)
        x_place += 40.466
        real_x += 40.466
    def ex_box(self):
        firefox = self.driver
        try:
            tag = firefox.find_element_by_xpath('//div[@id="viewbox"]')
            print(tag.is_displayed())
            return tag.is_displayed()
        except:
            return False
    

    截取到的数据截图

    接下来就要想办法对这些图片进行识别了~
    看了很多对于百度指数的爬虫文章到这一步很多都是直接使用pytesseract进行识别,我也使用过,感觉识别的准确率太低了,不知道是我的参数没写对还是怎么着,后来想着要不再训练一下这个模型,看了一下很多教程,要下载很多东西。就我感觉这些图片里面的文字信息还算简单,我就不用这个了,我打算自己重新训练一个模型。


    简单的文字识别模型

    我们先使用pil对图片进行切割,获取到我们最关心的那一部分信息。

    from PIL import Image
    for i in range(30):
        for k in range(3):
            im = Image.open('test/%s.png' % str(i))
            box = (left,upper,right,lower)
            new_im = im.crop(box)
            new_im.save('s/%s-%s.png' % (str(i),str(k)))
            left += change
            right += change
            text = pytesseract.image_to_string(new_im)
            print(text)
        left = 27
        right = 90
    
    图片.png

    最好对截取出来的图片进行二值化去掉不需要的可能存在的曲线背景。

    the_door = 180
    table = []
    for i in range(256):
        if i < the_door:
            table.append(1)
        else:
            table.append(0)
    
    cout = 0
    for i in range(1,6):
        for k in range(31):
            try:
                print('2011_0%s/%s.png' % (str(i),str(k)))
                im = Image.open('2011_0%s/%s.png' % (str(i),str(k)))
                size = im.size
                #new_im = im.crop((size[0]/2,size[1],size[0],size[1]))
                new_im = im.crop((83,35,128,50))
                size = new_im.size
                new_im = new_im.resize((size[0] * 10, size[1] * 10),Image.ANTIALIAS)
                new_im = new_im.convert('L')
                new_im = new_im.point(table,'1')
                size = new_im.size
                new_im = new_im.resize((int(size[0] / 10) , int(size[1]  / 10)),Image.ANTIALIAS)
                # datas = new_im.getdata()
                # print(datas)
                # newData = []
                # for item in datas:
                #     #print(item)
                #     if item == 1:
                #         newData.append(( 255, 255, 255, 0))
                #     else:
                #         newData.append(item)
                
                # new_im.putdata(newData)
                #img.save(dstImageName,"PNG")
                #print(new_im.size)
                #text = pytesseract.image_to_string(new_im)
                new_im.save('test/'+ str(cout) + '.png')
                #new_im.save('test/'+ text + '.png')
                #print(text)
                #break
                cout += 1
            except BaseException as e:
                print(e)
    
    
    

    截取出来图片之后我们就在ps中打开我们的这些图片,发现每个数字都是 6*10 的像素值,那么我们就可以截取出每个数字(尽量多些)
    之后我们再遍历我们这些图片中的像素点,以高宽遍历图片中的像素点,可以获得16个特征,6 + 10 每个特征辨识该高或宽遍历出的像素点个数

    #切割图片 获得百度指数中截取图片的每个数字
    def split_img(img_path):
        default1 = 3
        default2 = 3
        default3 = 9
        default4 = 13
        change = 6 + 2
        im = Image.open(img_path)
        result = []
        for k in range(5):
            try:
                new_im = im.crop((default1,default2,default3,default4))
                default1 += change
                default3 += change
                result.append(new_im)
            except BaseException as e:
                print(e)
        return result
    
    #对图片进行遍历 获取特征
    def get_feature(img_obj):
        pixel_cnt_list = []
        new_im = img_obj
        size = new_im.size
        for x in range(size[0]):
            pix_cnt_x = 0
            for y in range(size[1]):
                if new_im.getpixel((x,y)) == 0:
                    pix_cnt_x += 1
            pixel_cnt_list.append(pix_cnt_x)
        for y in range(size[1]):
            pix_cnt_y = 0
            for x in range(size[0]):
                if new_im.getpixel((x,y)) == 0:
                    pix_cnt_y += 1
            pixel_cnt_list.append(pix_cnt_y)
        def get_zero(x):
            return x != 0
        if len(list(filter(get_zero,pixel_cnt_list))) == 0 :
            return False
        string = '0' #重要! 这里如果没有训练出来自己的模型,需要先对上一函数分割出来的图片进行打标签,这个便是你的标签
        for item in range(len(pixel_cnt_list)):
            string += ' %s:%s' % (str(item + 1),str(pixel_cnt_list[item]))
        return string
    
    

    这里使用的是libsvm 我没有对它进行深入了解。。这里只是应用,对于python如何安装libsvm相信大家都能找到办法,在这里就不赘述了~

    通过数据,训练模型

    from libsvm.python.svmutil import *
    from libsvm.python.svm import *
    y, x = svm_read_problem('result.txt')
    model = svm_train(y, x)
    svm_save_model('predict.model', model)
    

    result.txt
    第一个值是标签,之后都是特征值,6+10 16个特征值:像素点
    这个我觉得大家应该都会构造吧...

    0 1:6 2:2 3:2 4:2 5:2 6:6 7:2 8:2 9:2 10:2 11:2 12:2 13:2 14:2 15:2 16:2
    0 1:6 2:2 3:2 4:2 5:2 6:6 7:2 8:2 9:2 10:2 11:2 12:2 13:2 14:2 15:2 16:2
    1 1:0 2:2 3:2 4:10 5:1 6:1 7:1 8:2 9:2 10:1 11:1 12:1 13:1 14:1 15:1 16:5
    2 1:5 2:3 3:3 4:3 5:3 6:4 7:4 8:2 9:2 10:1 11:2 12:1 13:1 14:1 15:1 16:6
    

    接下来我们只需要调用我们的模型,输入待识别图片的特征值便能识别出相应的数字,由于百度这个数字扭曲不大,所以这个模型的准确率还是很高的。。至少我现在还没碰到过不准的情况,测试集都是100%的正确率。。虽然测试只有100张图片。。
    识别调用

    def img_to_string(feature):
        if feature == False:
            return ''
        else:
            with open('temp.txt','w+') as f:
                f.write(feature)
            yt, xt = svm_read_problem('temp.txt')
            model = svm_load_model('value_predict.model')
            p_label, p_acc, p_val = svm_predict(yt, xt, model,options="-q")
            return p_label[0]
    
    
    def get_result(img_path):
        img_list = split_img(img_path)
        res = ''
        for i in img_list:
            fea = get_feature(i)
            result = img_to_string(fea)
            try:
                res += str(int(result))
            except:
                pass
        return res
    

    识别结果

    在这里百度指数的爬虫就告一段落,其实还可以优化得更好的。。但是先做其他的吧~
    如果有什么问题,或者有什么需要爬取的可以联系我哈~

    相关文章

      网友评论

        本文标题:从零实现百度指数爬虫~

        本文链接:https://www.haomeiwen.com/subject/jnygvftx.html