• 【保姆级爬虫】微博关键词搜索并获取博文和评论内容(python+selenium+chorme)


    微博爬虫记录

    写这个主要是为了防止自己忘记以及之后的组内工作交接,至于代码美不美观,写的好不好,统统不考虑,我只能说,能跑就不错了,上学压根没学过python好吧,基本上是crtl+c&ctrl+v丝滑小连招教会了我一点。

    写的很简单,认真看完就会用了

    文中筛选元素用到的一些筛选元素的正则匹配、beautifulsoup,css等相关方法我也不太懂,现学现用呗,还是那句话,能跑就行。

    配置简介
    python3.6、selenium3.13.0,chorme以及与chorme版本对应的chormedriver
    selenium在4版本后的一些语句会需要修改,网上一大把自己查)

    目录

    1、启动程序控制的chorme,手动登录微博
    2、在微博进行关键词的检索
    3、微博的发布信息获取
    4、保存数据
    5、实现自动翻页
    6、微博的评论信息获取

    1、先启动一个由程序控制的chorme

    (1)win+R,输入cmd打开命令行,输入代码进入chorme的安装位置

    C:\Program Files\Google\Chrome\Application
    
    • 1

    (2)分配chorme的端口号(我这里设置的是9527)和数据目录(我这里是在D:\selenium\AutomationProfile)

    chrome.exe --remote-debugging-port=9527 --user-data-dir="D:\selenium\AutomationProfile"
    
    • 1

    每次执行(1)(2)两行命令就能打开同一个chorme了,建议放在程序解析的最上方,这样浏览器关闭后下次可以通过命令行快速打开
    (3)浏览器已经打开了,登录一下自己的微博
    (4)链接一下程序和浏览器

    # 这部分代码我直接扔在了所有函数之前,搞全局
    # 把chormedriver的路径写到这里
    chromedriver_path= "D:/Users/16653/AppData/Local/Programs/Python/Python36/chromedriver.exe"
    option = ChromeOptions()
    option.add_experimental_option("debuggerAddress", "127.0.0.1:9527")
    web = webdriver.Chrome(executable_path=chromedriver_path, options=option)
    web.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": """
        Object.defineProperty(navigator, 'webdriver', {
          get: () => undefined
        })
      """
    })
    web.implicitly_wait(10)# 等待网页的加载时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2、进行关键词的检索

    从这里开始写函数的主体,自己搞个函数名把这些代码放进去
    关键词搜索的链接如下:

    https://s.weibo.com/weibo?q=这里填关键词&Refer=index
    
    • 1

    搜索页面翻页直接在后面加一个page=页码,如第二页

    https://s.weibo.com/weibo?q=这里填关键词&Refer=index&page=2
    
    • 1

    selenium获取初步搜索结果

    web.get(url) # url就是搜索的链接
    html = web.page_source
    print (html) # 输出当前程序获取到的网页信息,用于检查网页是否正常获取
    
    • 1
    • 2
    • 3

    在浏览器里点击右键选择检查,在浏览器里面可以用检查页面左上角的框框箭头符号方便得在左边选择图案或者文字,然后实时在右边看到这个被选择的要素在哪个标签

    可以发现每个博文都在action-type="feed_list_item"的div标签下(结合下图左右蓝色部分理解,标签就是一个<>包含的东西)这是一个很重要的地方,学会用浏览器的这个功能选取自己需要的信息在哪个标签里面,下面所有获取信息的代码几乎都是基于此

    在这里插入图片描述
    那我们可以通过beautifulsoup的findAll函数把所有这些标签的内容选择出来放进list(其他标签下提取信息也适用这个函数哦,只需要对应修改div,action-type,feed_list_item就行)
    如果只想要找到的第一个div标签下的信息就用find函数,而不是findAll

    soup = bs(html, 'html.parser')
    list = soup.findAll("div", {'action-type': "feed_list_item"})
    
    • 1
    • 2

    3、解析多种数据

    (1)获取博文的文本内容,微博的文章字数太长会收起来,为了文本内容获取完整必须先将所有文本展开。
    在这里插入图片描述

     	web_object = {}
        html = web.page_source
        # 获取这一网页的所有未展开的文章的展开按钮
        button_list = web.find_elements_by_css_selector('a[action-type="fl_unfold"]') #点击所有展开
        # 在for循环里面每个都点击展开
        for bt in button_list:
            try :
                bt.click()
            except Exception as e:
                print(e.args)
    
        # html转beautifulsoup格式
        soup = bs(html, 'html.parser')
        # 已经展开了,开始正常获取这一页的微博列表list
        list = soup.findAll("div", {'action-type': "feed_list_item"})
        for i in list:
            # 获取微博的文本信息,strip用于跳过字符前面的空白
            txt = i.findAll("p", {'class':"txt"})[-1].get_text().strip()
            print(txt) # 输出获得的内容
            web_object['text'] = txt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    (2)获取微博的mid

        mid = i.get("mid")
        print("mid",mid)
        web_object['mid'] = mid
    
    • 1
    • 2
    • 3

    (3)获取发布者的昵称

      user_name = i.find("a", {'class': "name"}).get_text()#名字放在class为name的a标签里面
      print("昵称", user_name)
      web_object['user_name'] = user_name
    
    • 1
    • 2
    • 3

    (4)获取时间

            i = i.find("div",{'class':"card"})
            itime = i.find("div", {'class': "from"})
            uptime = itime.find("a").get_text().strip()
            print("发布时间:", uptime)
            web_object['date'] = uptime
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (5)点赞、评论、转发的人数

    		cardact = i.find("div", {'class': "card-act"})
            repost_num = cardact.findAll("li")[0].get_text().strip()
            if repost_num =="转发":
                repost_num = 0
            print("转发人数:", repost_num)
            web_object['repost_num'] = repost_num
    
            comment_num = cardact.findAll("li")[1].get_text().strip()
            if comment_num == "评论":
                comment_num = 0
            print("评论人数:", comment_num)
            web_object['comment_num'] = comment_num
    
            like_num = cardact.findAll("li")[2].get_text().strip()
            if like_num == "赞":
                like_num =0
            print("点赞人数:", like_num)
            web_object['like_num'] = like_num
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (6)获取更多关于微博博主的信息
    需要先从微博的搜索页面跳转到用户的界面,获取完信息后需要再跳转回来

     # 控制跳转
     user_link=i.find("a").get("href")
     print("用户主页:", user_link)
     web_object['user_link'] = user_link
     # 拼出用户的主页链接user_url
     user_url = "'" + "https:" + user_link+ "'"
     js = "window.open(" + user_url + ");"
     web.execute_script(js)
     time.sleep(random.randint(2, 5))
     # 切换到新窗口
     # 获得打开的第一个窗口句柄
     window_1 = web.current_window_handle
     # 获得打开的所有的窗口句柄
     windows = web.window_handles
     # 切换到最新的窗口
     for current_window in windows:
         if current_window != window_1:
              web.switch_to.window(current_window)
    
      html = web.page_source
      soup = bs(html, 'html.parser')
      print("切换到用户主页")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    (7)获取用户类型(红V,蓝V,黄V等)
    同样的,先获取这个类型所在的网页标签,然后得到具体内容,如果没有红V,蓝V,黄V则程序执行异常,通过try语句抓取异常,将用户类型设置为普通用户

            try:
                typehtml = soup.find("div",{'class':"woo-avatar-main woo-avatar-hover ProfileHeader_avatar2_1gEyo"})
                user_type = typehtml.find("span").get("title")
                web_object['user_type'] = user_type
                print("用户类型", user_type)
            except AttributeError as e:
                web_object['user_type'] = '普通用户'
                print("用户类型:", '普通用户')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (8)获取用户性别

            genderhtml = soup.find("div",{'class': "woo-box-flex woo-box-alignCenter ProfileHeader_h3_2nhjc"})
            gender = genderhtml.find("span").get("title").strip()
            print("用户性别:", gender)
            web_object['gender'] = gender
    
    • 1
    • 2
    • 3
    • 4

    (9)获取用户的粉丝关注人数

    fanshtml = soup.find("div",{'class': "woo-box-flex woo-box-alignCenter ProfileHeader_h4_gcwJi"})
    followers = fanshtml.findAll("a")[0].get_text().strip('粉丝')
    followers =followers[3:]
    follow = fanshtml.findAll("a")[1].get_text().strip('关注')
    follow = follow[3:]
    print("粉丝数量:",followers,"关注人数:",follow)
    web_object['follow'] = follow
    web_object['followers'] = followers
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (10)获取用户的ip以及年龄

    地址有两种,一种直接在用户界面写ip属地后面接ip地址,另一种是地点的小图标后接ip地址,所以需要先判断是不是能用第一种拿到ip,不行就用第二种,都不行就是没有ip

    浏览器检查网页可以发现,展开后的一条条的信息标签都是一样的,不好区分,这里采用获取文本信息后进行关键词的监测来从中获取ip属地和出生日期

    在这里插入图片描述

           # 点击展开用户主页信息
            button = web.find_element_by_xpath('//*[@id="app"]/div[2]/div[2]/div[2]/main/div/div/div[2]/div[1]/div[1]/div[3]/div/div/div[2]')
            button.click()
            html = web.page_source
            soup = bs(html, 'html.parser')
            ipflag = 0# 为1时表示已经获取ip信息
            infohtml = soup.findAll("div",{'class': "woo-box-item-flex ProfileHeader_con3_Bg19p"})
            for info in infohtml:
                str1 = str(info.get_text()).strip()
                print(str1)
                try:
                    if (keywords_check('加入微博', str1)):#日期后面跟一个“加入微博”,排除
                        print("加入微博时间,不是生日")
                    else:	# 正则匹配日期格式
                        result = re.findall("\d{4}[-|.|/]?\d{2}[-|.|/]?\d{2}", str1)
                        if(result):
                            print("生日", result[0])
                            age = age_calc(result[0], '2024-01-06')#网上搜的一个生日计算的方法
                            print(age)
                            web_object['age'] = age
                except TypeError as e:
                    print('')
                if (keywords_check('IP属地', str1)):# 检查用户主页是否有ip属地这种格式的ip
                    ipflag = 1	# 有的话标记为1,表示已经拿到ip
                    ip = str1[5:] # 将(ip属地:)五个字符去掉,保留后面的地址
                    print("地址", ip)
    
            if ipflag == 0: #如果前面没找到ip属地,则找ip图标来判断
                try:
                    iphtml = soup.find("div",{'class':"ProfileHeader_box3_2R7tq"})
                    ip = iphtml.find("i",{'class':"woo-font woo-font--proPlace"}).parent.parent.get_text()
                    print("ip:", ip)
                except AttributeError as e:
                        print("该用户没有ip信息")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    网上随便找的根据日期计算生日的方法

    def age_calc(birth_date, end_date):
        # 将日期转化为datetime类型
        birth_date = datetime.strptime(birth_date, '%Y-%m-%d')
        end_date = datetime.strptime(end_date, '%Y-%m-%d')
        # 分别计算年月日
        day_diff = end_date.day - birth_date.day
        month_diff = end_date.month - birth_date.month
        year_diff = end_date.year - birth_date.year
    
        if day_diff >= 0:
            if month_diff >= 0:
                years_old = year_diff
            else:
                years_old = year_diff - 1
        else:
            if month_diff >= 1:
                years_old = year_diff
            else:
                years_old = year_diff - 1
    
        return years_old
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    检查关键词的方法,keywords是要找的关键词,text是全部文本

    def keywords_check(keyswords, text):
      keyswords
      result = re.search(keyswords, text)
      return result
    
    • 1
    • 2
    • 3
    • 4

    4、保存数据

    #  保存数据
    def save_data(web_data,filename):
        fieldnames = ['mid', 'text', 'date', 'user_name', 'gender', 'follow', 'followers','age','ip','user_type','user_link','repost_num','comment_num','like_num']
        with open(filename, mode='a', newline='', encoding='utf-8-sig') as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            # 判断表格内容是否为空,如果为空就添加表头
            if not os.path.getsize(filename):
                writer.writeheader()  # 写入表头
            writer.writerows([web_data])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如此一来,一个微博内容以及发布者的信息获取就完成了,我们保存数据后再次切换网页到搜索页面,在此之后就是继续对前文提到的微博列表list里面的下一个微博进行上述处理,文章3.1写到的for已经帮助我们进行了循环功能,所以这下面的代码放在for循环里面就可以了

            save_data(web_object, filename)# 保存
            web.close()
            # 回到主页面
            web.switch_to.window(web.window_handles[0])
    
    • 1
    • 2
    • 3
    • 4

    5、搜索结果翻页

    前面我们只实现了一页网页的数据,下面实现翻页

        page_count = 50  #  总页数设置
        for page in range(page_count):
            print('开始获取第%d页的搜索结果'%(page+1))
            temp =str(page+1)
            url ='https://s.weibo.com/weibo?q=关键词&Refer=index&page=%s'%(temp)
            filename = os.getcwd()+'/data/微博/搜索结果.csv' # 没有目录就新建一个目录
            start_crawler(url, filename)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将以上内容串起来
    下面的start_crawler函数就是爬虫的主体,也就是写
    web.get(url)
    print(“=开始了开始啦====”)
    html = web.page_source
    等等等上述代码

    if __name__ == '__main__':
        # 测试用的url链接
        page_count = 50  #  总页数设置
        for page in range(page_count):# for循环进行翻页
            print('开始获取第%d页的搜索结果'%(page+1))
            temp =str(page+1)
            url ='https://s.weibo.com/weibo?q=关键词&Refer=index&page=%s'%(temp)
            filename = os.getcwd()+'/data/关键词.csv' # 没有目录就先新建一个目录
            start_crawler(url, filename)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    6、微博的评论信息获取

    这是另一个python文件了,这里用的评论链接获取的网页和前面的网页不同,是json格式的数据,解析数据时用到的方法不同,详情可以自行搜索

    6.1先看主函数
    read_mid读取目标微博的mid号列表,然后用mid拼成每条微博评论所在的链接url,通过start_crawler爬取每条微博的所有评论

    if __name__ == '__main__':
        # 测试用的url链接
        filename = r"放你上面步骤爬取得到的的文件路径"
        write_path = r"随意路径\微博评论.csv"
        midlist = read_mid(filename)
        count = 1
        for i in midlist:
             if count >9:
                print("====================第%d篇微博==============="%count)
                mid = i[3:]
                url = 'https://m.weibo.cn/comments/hotflow?id=%s&mid=%s'%(mid,mid)
                print(url)
                start_crawler(url, write_path,i)# 也存一下评论属于哪一条微博
             count = count+1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    6.2 read_mid函数

    def read_mid(filename):
        data = pd.read_csv(filepath_or_buffer=filename, encoding="utf-8",converters={"mid":str})
        return data['mid'][data['comment_num']>=10]  # 获取评论数不少于10的微博mid
    
    
    • 1
    • 2
    • 3
    • 4

    6.3 start_crawler函数
    涉及到评论翻页的问题,评论翻页用max_id来标记(微博自己设置的),为0时翻页完毕
    将获取的网页数据识别成json处理

    def start_crawler(url,filename,mid):
        comment_url = url
        max_id = 1 # 评论翻页的位置
        page = 1
        while (max_id):
            if max_id == 0:
                break
            elif max_id != 1:
                url = comment_url+'&max_id='+str(max_id)
                print(url)
            print("======================第%d页===================="%page)
            web.get(url)
            time.sleep(random.randint(3, 6)) # 不要爬取太快哦,小心被关进小黑屋
            html = web.page_source
            soup = bs(html, 'lxml')
            ss = soup.select('pre')[0]
            res = json.loads(ss.text)  # 转json格式
            max_id = get_info(web,res,filename,mid)  # 获取一页评论,并且返回max_id用于翻页
            page = page+1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    6.3 get_info 函数
    不太想解释了,下次有空再解释吧
    计算年龄的函数同上文

    
    def get_info(web,res,filename,mid):
        try :
            datalist = res['data']['data']
        except KeyError as e:
            return
        max_id = res['data']['max_id']
        for i in datalist:
            web_object = {}
            web_object['mid'] = mid
    
            print("\033[34m发表时间:\033[0m" + i['created_at'])
            web_object['date'] = i['created_at']
    
            print("\033[35m评论内容:\033[0m" + i['text'])
            web_object['text'] = i['text']
    
            print("\033[36m位置:\033[0m" + str(i['source'])[2:])
            web_object['ip'] = str(i['source'])[2:]
    
            print("\033[36m昵称:\033[0m" + i['user']['screen_name'])
            web_object['user_name'] = i['user']['screen_name']
            
            print("\033[31m个签:\033[0m" + i['user']['description'])
            web_object['status'] = i['user']['description']
    
            user_id = '%d' % (i['user']['id'])
            print("\033[37mid号:\033[0m" + user_id)
            web_object['user_id'] = user_id
    
            user_link = "https://weibo.com/"+user_id+ '?refer_flag=1001030103_'
            print("\033[36m用户主页:\033[0m" + user_link)
            web_object['user_link'] = user_link
    
            print("\033[32m性别:\033[0m" + i['user']['gender'])
            web_object['gender'] = i['user']['gender']
    
            user_follow_count = '%d' % (i['user']['follow_count'])
            print("\033[31m关注人数:\033[0m" + user_follow_count)
            web_object['follow'] = user_follow_count
    
            user_followers_count = (i['user']['followers_count'])
            print("\033[31m被关注人数:\033[0m" + user_followers_count)
            web_object['followers'] = user_followers_count
    
            # =========================================切换到用户主页的窗口
            user_url = "'" + user_link + "'"
            js = "window.open(" + user_url + ");"
            web.execute_script(js)
            time.sleep(random.randint(2, 5))
            # 切换到新窗口
            # 获得打开的第一个窗口句柄
            window_1 = web.current_window_handle
            # 获得打开的所有的窗口句柄
            windows = web.window_handles
            for current_window in windows:
                if current_window != window_1:
                    web.switch_to.window(current_window)
            html = web.page_source
            soup = bs(html, 'html.parser')
            print("切换到用户主页")
            web.implicitly_wait(4)
            try:
                typehtml = soup.find("div", {'class': "woo-avatar-main woo-avatar-hover ProfileHeader_avatar2_1gEyo"})
                user_type = typehtml.find("span").get("title")
                web_object['user_type'] = user_type
                print("用户类型:", user_type)
            except AttributeError as e:
                web_object['user_type'] = '普通用户'
                print("用户类型:", '普通用户')
            # ============================点击展开============================
            button = web.find_element_by_xpath(
                '//*[@id="app"]/div[2]/div[2]/div[2]/main/div/div/div[2]/div[1]/div[1]/div[3]/div/div/div[2]')
            button.click()
            html = web.page_source
            soup = bs(html, 'html.parser')  # woo-box-item-inlineBlock ProfileHeader_item3_1bUM2,woo-box-item-flex ProfileHeader_con3_Bg19p
            infohtml = soup.findAll("div", {'class': "woo-box-item-flex ProfileHeader_con3_Bg19p"})
            # print(infohtml)
            for info in infohtml:
                str1 = str(info.get_text()).strip()
                # print(str1)
                try:
                    if (keywords_check('加入微博', str1)):
                        print("加入微博时间,不是生日")
                    else:
                        result = re.findall("\d{4}-\d{2}-\d{2}", str1)
                        if (len(result) != 0):
                            try :
                                print("生日:", result[0])
                                age = age_calc(result[0], '2024-01-06')
                                print(age)
                                web_object['age'] = age
                            except ValueError as e:
                                print('')
                except TypeError as e:
                    print('没有年龄信息')
    
            # sys.exit()  # 退出当前程序,但不重启shell
            time.sleep(random.randint(2, 6))
            web.close()
            # 回到主页面
            web.switch_to.window(web.window_handles[0])
            save_data(web_object, filename)
    
        print(max_id)
        return max_id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
  • 相关阅读:
    人工智能基础 | 回归分析(四)
    总结 : 毕设采访原文呈现
    MySQL常用函数
    Kudu-1.16编译中下载Gradle依赖失败的解决办法
    计算机竞赛 深度学习 opencv python 公式识别(图像识别 机器视觉)
    Android测试常用的adb命令
    Keras可视化神经网络架构的4种方法
    江川第一镰启动仪式 国稻种芯:北大荒2300余万亩水稻收获
    C/C++教程 从入门到精通《第十五章》—— MFC资源详解
    hive实战项目:旅游集市数仓建设
  • 原文地址:https://blog.csdn.net/QianQing_mio/article/details/135461277