예제 #1
0
def parser_feedback_json(feedback_json):
    """ [数据] 解析推文反馈数据
    :param feedback_json: <dict> 推文反馈数据所在Json数据
    :return: <dict:str-dict> 推文反馈数据对应表(key=推文ID,value=推文ID对应的推文反馈Json数据) / <None> 推文反馈数据所在Json数据异常
    """
    if feedback_json is None:
        tool.console("报错", "推文反馈数据的Json格式异常,怀疑用户类型错误")
        return None

    if "pre_display_requires" not in feedback_json:
        tool.console("报错", "推文反馈数据的Json中未找到pre_display_requires属性")
        return None

    feedback_dict = {}  # 定义推文反馈数据对应表
    len(feedback_json["pre_display_requires"])
    for item_list in feedback_json["pre_display_requires"]:
        if not isinstance(item_list, list) or len(item_list) == 0:
            continue
        if item_list[0] == "RelayPrefetchedStreamCache":
            feedback_item = item_list[3][1]["__bbox"]  # 读取推文反馈Json数据
            tweet_id = feedback_item["result"]["data"]["feedback"][
                "subscription_target_id"]  # 读取推文ID
            feedback_dict[tweet_id] = feedback_item

    return feedback_dict
예제 #2
0
def crawler(mysql, table_name="weibo", test=False):
    # 执行网页请求
    headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Host": "s.weibo.com",
        "Pragma": "no-cache",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "cross-site",
        "Sec-Fetch-User": "******",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
    }
    response = requests.get("https://s.weibo.com/top/summary", headers=headers)  # 请求微博热搜榜
    bs = BeautifulSoup(response.content.decode(errors="ignore"), 'lxml')

    # 解析网页
    hot_list = list()
    empty_rank = 0  # 统计空热搜(广告热搜)数量
    for label_item in bs.select("#pl_top_realtimehot > table > tbody > tr"):  # 遍历热搜的标签
        # 提取热搜排名
        if label_rank := label_item.select_one("tr > td.td-01"):
            if len(label_rank.text) == 0:
                continue
            if match_rank := re.search("[0-9]+", label_rank.text):
                ranking = int(match_rank.group()) - empty_rank
            else:
                tool.console("报错", "提取的热搜排名不包含数字!")
                continue
예제 #3
0
def parser_tweet(label, feedback_dict, template):
    """ [数据] 解析推文标签中所有内容并返回推文的Item对象
    :param label: <bs4.element.Tag> 推文标签:<div._4-u2._4-u8>
    :param feedback_dict: <dict:str-dict> 推文反馈数据对应表
    :param template: None
    :return: <crawler.item.Item> Facebook推文的Item对象
    """
    item = copy.deepcopy(template)  # 构造Facebook推文的Item对象
    label_wrapper = label.select_one(
        "div._4-u2._4-u8 > div > div > div > div.userContentWrapper"
    )  # 定位到推文外层标签(第2层)
    if label_wrapper is None:
        tool.console("报错", "推文外层标签定位失败,放弃当前推文抓取")
        return None
    label_main = locate_main_label(
        label_wrapper, item)  # [定位] 定位到推文标题标签(第3层) + [数据] 提取推文正文标签中的数据:推文是否置顶
    if label_main is not None:
        label_title = locate_title_label(label_main)  # [定位] 定位到推文标题标签(第4层)
        get_from_title(
            label_title,
            item)  # [数据] 提取推文标签标题中的数据:包括推文ID、推文Url、推文是否为分享、推文发布者、推文发布时间
        label_content = label_main.select_one(
            "div." + label_main["class"] +
            ' > div:nth-child(2)')  # 定位到推文内容标签(第4层)
        item["content"] = label_content.text  # [数据] 提取推文内容标签中的数据:推文内容
        if item["tweet_id"] is None:
            search_tweet_id(label_wrapper, item)  # [数据] 再次提取:推文ID
        get_from_feedback(
            feedback_dict,
            item)  # [数据] 提取推文反馈Json数据中的数据:包括推文Url、推文点赞数、推文评论数、推文转发数、推文视频播放数
        return item
    else:
        print("解析推文对象异常!")
        return None
예제 #4
0
def get_ajax_href(soup):
    """ [数据] 提取Ajax请求的Url
    :param soup: <bs4.BeautifulSoup> / <bs4.element.Tag> 包含Ajax请求Url所在标签的BeautifulSoup对象或Tag对象
    :return: <str> Ajax请求Url / <None> 不包含
    """
    # [定位] 定位到Ajax请求Url所在标签(第2层)
    label = soup.select_one(
        "#www_pages_reaction_see_more_unitwww_pages_posts > div > a")

    # [数据] 提取:Ajax请求Url所在的ajaxify属性
    ajax_href = label["ajaxify"]  # 读取Ajax请求Url所在属性
    if ajax_href.is_empty():
        tool.console("报错", "获取Ajax请求的Url失败:" + str(label))
        return None

    # [数据] 处理:依据ajaxify属性计算Ajax请求的Url
    other_param = "&fb_dtsg_ag&__user=0&__a=1&__csr=&__req=c&__be=1&__pc=PHASED%3ADEFAULT&dpr=1&__rev=1001280609"
    return "https://www.facebook.com" + str(ajax_href) + other_param
예제 #5
0
def get_from_title(title_label, item):
    """ [数据] 提取推文标签标题中的数据:包括推文ID、推文Url、推文是否为分享、推文发布者、推文发布时间
    :param title_label: <bs4.element.Tag> 推文标题标签
    :param item: <crawler.item.Item> Facebook推文的Item对象
    :return: <None> 更新结果于Facebook推文的Item对象
    """
    title_class = title_label["class"]  # 获取推文标签标题的class属性用于定位

    # [数据] 提取:推文ID
    selector_tweet_id = "div." + title_class + " > div"
    tweet_id_source = title_label.select_one(selector_tweet_id)["id"]  # 提取推文ID
    if re.search("(?<=;)[0-9]+(?=;;)", tweet_id_source):
        item["tweet_id"] = re.search(
            "(?<=;)[0-9]+(?=;;)", tweet_id_source).group()  # 整理推文ID(提取其中数字部分)

    # [数据] 提取:推文发布者
    selector_author = "div." + title_class + " > h5"
    author = title_label.select_one(selector_author).text  # 提取推文发布者

    # [数据] 提取:推文发布时间
    selector_time = "div." + title_class + " > div > span > span > a > abbr"
    item["time"] = int(
        title_label.select_one(selector_time)["data-utime"])  # 提取推文发布时间戳

    # [数据] 计算:推文是否为分享
    if "分享" in author:  # 判断推文是否为分享
        item["is_share"] = True
        # 读取分享推文的推文发布者
        selector_author = "div." + title_class + " > h5 > span > span > a"
        author_source = title_label.select_one(selector_author)[
            "href"]  # 推文发布者名称所在属性
        if "直播视频" in author:  # 如果转载内容包括"直播视频",则推文发布者为直播视频
            item["author"] = "[直播视频]"
        elif author_source is not None:  # 判断是否提取到分享来源用户
            if re.search("(?<=^/)[^/]+",
                         author_source) is not None:  # 在发布者名称所在属性中检索发布者的主页名称
                item["author"] = re.search("(?<=^/)[^/]+",
                                           author_source).group()
            else:
                tool.console("警告", "转发推文来源可能不是Facebook用户:" + author)
    else:
        item["author"] = author  # 若推文不是分享,则填写推文发布者
예제 #6
0
def get_from_feedback(feedback_dict, item):
    """ [数据] 提取推文反馈Json数据中的数据:包括推文Url、推文点赞数、推文评论数、推文转发数、推文视频播放数
    :param feedback_dict: <dict:str-dict> 推文反馈数据对应表
    :param item: <crawler.item.Item> Facebook推文的Item对象
    :return: <None> 更新结果于Facebook推文的Item对象
    """
    if item.get("tweet_id") is None or item.get(
            "tweet_id") not in feedback_dict:
        tool.console("报错",
                     "未能在推文反馈Json数据中找到的推文ID:" + str(item.get("tweet_id")))
        return None
    feedback_item = feedback_dict[item.get("tweet_id")]
    item["tweet_url"] = feedback_item["result"]["data"]["feedback"][
        "url"]  # 提取:推文Url
    item["reaction"] = feedback_item["result"]["data"]["feedback"][
        "reaction_count"]["count"]  # 提取:推文点赞总数
    item["comment"] = feedback_item["result"]["data"]["feedback"][
        "comment_count"]["total_count"]  # 提取:推文评论总数
    item["share"] = feedback_item["result"]["data"]["feedback"]["share_count"][
        "count"]  # 提取:推文分享总数
예제 #7
0
    # 解析网页
    hot_list = list()
    empty_rank = 0  # 统计空热搜(广告热搜)数量
    for label_item in bs.select("#pl_top_realtimehot > table > tbody > tr"):  # 遍历热搜的标签
        # 提取热搜排名
        if label_rank := label_item.select_one("tr > td.td-01"):
            if len(label_rank.text) == 0:
                continue
            if match_rank := re.search("[0-9]+", label_rank.text):
                ranking = int(match_rank.group()) - empty_rank
            else:
                tool.console("报错", "提取的热搜排名不包含数字!")
                continue
        else:
            tool.console("报错", "未提取到热搜排名!")
            continue

        # 提取热搜关键词
        if label_keyword := label_item.select_one("tr > td.td-02 > a"):
            keyword = label_keyword.text
        else:
            tool.console("报错", "未提取到热搜关键词!")
            continue

        # 提取热搜热度
        if label_heat := label_item.select_one("tr > td.td-02 > span"):
            if match_heat := re.search("[0-9]+", label_heat.text):
                heat = int(match_heat.group())
            else:
                tool.console("报错", "提取的热搜热度不包含数字!")
예제 #8
0
def crawler(posts_url, time_start, time_end, template):
    """ 抓取Facebook用户指定时间范围内的所有推文
    :param posts_url: <str> Facebook用户帖子页Url
    :param time_start: <int> 时间范围开始时间戳
    :param time_end: <int> 时间范围结束时间戳
    :return: <list:crawler.item.Item> Facebook推文的Item对象列表
    """
    tweet_list = []  # Facebook推文的Item对象列表
    ajax_url = None  # [数据] 提取Ajax请求的Url

    for i in range(200):  # 最多对每个账号执行200次请求(大概最多1600条推文)
        if i == 0:  # 第一次请求Facebook用户帖子页
            try:
                response = requests.get(posts_url, proxies=env.VPN_PROXY)
            except OSError or ConnectionResetError or ProxyError or MaxRetryError:
                tool.console("报错", "请求失败,网络可能出现问题,请检查")
                return tweet_list
            if response:
                bs = BeautifulSoup(response.content.decode(errors="ignore"),
                                   'lxml')  # 将帖子页HTML转换为BeautifulSoup对象
                feedback_dict = parser_feedback_json(locate_feedback_json(
                    bs))  # [数据] 提取推文反馈数据所在Json数据 + 解析推文反馈数据
                label_list = bs.select(
                    "#pagelet_timeline_main_column > div > div:nth-child(2) > div > div._4-u2._4-u8"
                )
            else:
                return tweet_list
        else:  # 后续的请求请求
            if ajax_url is None:
                tool.console("报错", "Ajax的Url为空,结束当前用户的抓取")
                return tweet_list
            try:
                response = requests.get(
                    ajax_url, proxies=env.VPN_PROXY)  # 请求Facebook用户Ajax更新页
            except OSError or ConnectionResetError or ProxyError or MaxRetryError:
                tool.console("报错", "请求失败,尝试等待20秒后重新请求")
                time.sleep(20)
                try:
                    response = requests.get(
                        ajax_url, proxies=env.VPN_PROXY)  # 请求Facebook用户Ajax更新页
                except OSError or ConnectionResetError or ProxyError or MaxRetryError:
                    tool.console("报错", "尝试等待20秒后请求再次失败,放弃当前媒体抓取")
                    return tweet_list
            if response:
                ajax_json = json.loads(
                    response.content.decode(errors="ignore").replace(
                        "for (;;);", ""))
                if ajax_json is not None:
                    bs = BeautifulSoup(ajax_json["domops"][0][3]
                                       ["__html"])  # 将HTML部分转换为BeautifulSoup对象
                    feedback_dict = parser_feedback_json(
                        ajax_json["jsmods"])  # [数据] 解析推文反馈数据
                    label_list = bs.select(
                        "div._1xnd > div._4-u2._4-u8")  # [定位] 定位到推文标签列表
                else:
                    return tweet_list
            else:
                return tweet_list

        tool.console("运行",
                     "执行第" + str(i + 1) + "次请求,返回数量: " + str(len(label_list)))

        # 解析推文标签数据
        for label in label_list:  # 遍历推文标签列表
            tweet_info = parser_tweet(label, feedback_dict,
                                      template)  # 解析推文标签中所有内容并返回推文的Item对象
            if tweet_info is None:
                continue
            if tweet_info["time"] == 0:  # 若推文时间抓取失败则跳过当前推文
                continue
            elif tweet_info["time"] > time_end:  # 推文晚于抓取时间范围结束时间的情况(跳过当前推文)
                continue
            elif tweet_info["time"] >= time_start:  # 推文处于抓取时间范围的情况(保存当前推文)
                tweet_list.append(tweet_info)  # [数据] 解析推文标签中所有内容并返回推文的Item对象
            else:  # 推文早于抓取时间范围开始时间的情况(跳出当前用户)
                if not tweet_info["is_sticky"]:
                    return tweet_list

        ajax_url = get_ajax_href(bs)  # [数据] 提取Ajax请求的Url
        time.sleep(3)  # 延时3秒

    return tweet_list
예제 #9
0
    # 读取榜单媒体名录中的Facebook账号Url列表
    media_list = mysql_catalogue.select("media_list",
                                        ["media_id", "media_name", "media_fb"])

    for media in media_list:
        # 判断媒体是否有Facebook用户主页Url
        if media[2] is None or media[2] == "" or media[2] == "None":
            continue

        # 判断媒体主页是否存在异常(或需要登录无法抓取)
        if media[0] in [
                16, 38, 46, 189, 229, 244, 249, 296, 310, 332, 338, 344, 390,
                435, 471, 472, 538, 552, 577
        ]:
            tool.console("报错",
                         media[1] + "(" + str(media[0]) + ")为页面异常媒体,已跳过该媒体")
            continue

        facebook_name = get_facebook_user_name(
            media[2])  # 依据Facebook用户主页Url计算用户名称
        posts_url = "https://www.facebook.com/pg/{}/posts/?ref=page_internal".format(
            facebook_name)  # 依据用户名称计算用户帖子页Url

        tool.console("运行", "开始抓取媒体:" + media[1] + "(" + str(media[0]) + ")")

        template = {
            "media_id": media[0],  # 媒体ID
            "media_name": media[1],  # 媒体ID
            "cr_month": 2006,  # 抓取所属时间范围
            "tweet_id": None,  # 推文ID
            "tweet_url": None,  # 推文Url