6.2 获取分享的数量和内容
在开始探索哪些特征会使得内容易于共享之前,我们手头上需要足够的内容。我们还需要每份内容在各种社交网络上的分享次数。幸运的是,获取这些并没有多大困难。我会使用网站ruzzit.com。
这是一个相对较新的网站——它仍处于beta测试阶段[2],但它会跟踪最常被分享的内容,这正是我们需要的,如图6-2所示。
我们将从页面中抓取内容——不幸的是,没有可以直接使用的API接口。而且,因为该网站使用了无限滚动的机制,我们需要使用第3章的老朋友,Selenium和PhantomJS。现在开始抓取吧。
图6-2
我们将从最初需要导入的包开始。
import requests import pandas as pd import numpy as np import json import time from selenium import webdriver pd.set_option('display.max_colwidth', 200)
接下来,我们将设置Selenium浏览器。这里仅选择该站点过去一年的文章来生成URL网址列表。需要设定浏览器的大小,以便获得标准的桌面外观,并且每隔15秒请求一次,这事关礼仪[3]。我们也将向下滚动相当于50页的内容(每页有10篇文章)。
browser = webdriver.PhantomJS() browser.set_window_size(1080,800) browser.get("http://www.ruzzit.com/en-US/Timeline?media=Articles&timeline= Year1&networks=All") time.sleep(3) pg_scroll_count = 50 while pg_scroll_count: browser.execute_script("window.scrollTo(0, document.body.scrollHeight);") time.sleep(15) pg_scroll_count -= 1 titles = browser.find_elements_by_class_name("article_title") link_class = browser.find_elements_by_class_name("link_read_more_article") stats = browser.find_elements_by_class_name("ruzzit_statistics_area")
在最后一节中,我们选择了分析所需的页面元素。接下来,需要进一步解析它们以获取文本信息。
我在分析中去除Twitter所提供的分享次数。该公司在2015年年底决定从其标准API中删除此项数据。鉴于此,其展示的次数不太可靠。为了避免数据被污染的风险,最好直接去除这些信息。
all_data = [] for title, link, stat in zip(titles, link_class, stats): all_data.append((title.text,\ link.get_attribute("href"),\ stat.find_ element_by_class_name("col-md- 12").text.split(' shares')[0], stat.find_element_by_class_name("col-md- 12").text.split('tweets\n') [1].split('likes\n0')[0], stat.find_element_by_class_name("col-md- 12").text.split('1's\n')[1].split(' pins')[0], stat.find_element_by_class_name("col-md- 12").text.split('pins\n')[1]))
接下来,我们将它放入一个数据框。
df = pd.DataFrame(all_data, columns=['title', 'link', 'fb', 'lnkdn', 'pins', 'date']) df
上述代码生成图6-3的输出。
图6-3
这是一个好的开始,但我们需要清理数据。你会注意到所有的链接都是通过ruzzit.com的重定向。我们将通过跟踪链接,检索原始站点的链接来解决这个问题,具体如下。
df = df.assign(redirect = df['link'].map(lambda x: requests.get(x).url))
此行代码使用requests库检索故事的真实URL(重定向之后的),如图6-4所示。
图6-4
如果现在检查数据框DataFrame,我们可以看到该网站的原始链接。你会注意到在第一行有CNN的主页。仔细研究后,发现有17个故事指向某个网站的主页。这是因为它们已被删除。另一个原因是一些链接指向的是图像而不是文章。
以下代码将识别这两个问题,并删除有问题的行。
def check_home(x): if '.com' in x: if len(x.split('.com')[1]) < 2: return 1 else: return 0 else: return 0 def check_img(x): if '.gif' in x or '.jpg' in x: return 1 else: return 0 df = df.assign(pg_missing = df['pg_missing'].map(check_home)) df = df.assign(img_link = df['redirect'].map(check_img)) dfc = df[(df['img_link']!=1)&(df['pg_missing']!=1)] dfc
上述代码生成图6-5的输出。
图6-5
现在让我们进行下一步,获取完整的文章和其他元数据。就如上一章,我们将使用embed.ly中的API接口。如果对于其设置你需要帮助,请返回前一章参看详细介绍。这里将使用embed.ly来检索文章的标题、HTML和一些附加数据,例如引用和图像。
def get_data(x): try: data = requests.get('https://api.embedly.com/1/extract? key=SECRET_ KEY7&url=' + x) json_data = json.loads(data.text) return json_data except: print('Failed') return None dfc = dfc.assign(json_data = dfc['redirect'].map(get_data)) dfc
上述代码生成图6-6的输出。
图6-6
现在每篇文章都有一列JSON数据。这里将解析这个JSON数据,抽取出我们有兴趣探索的每个特征列。先从基本信息开始:网站、标题、HTML和图片数量。
def get_title(x): try: return x.get('title') except: return None def get_site(x): try: return x.get('provider_name') except: return None def get_images(x): try: return len(x.get('images')) except: return None def get_html(x): try: return x.get('content') except: return None dfc = dfc.assign(title = dfc['json_data'].map(get_title)) dfc = dfc.assign(site = dfc['json_data'].map(get_site)) dfc = dfc.assign(img_count = dfc['json_data'].map(get_images)) dfc = dfc.assign(html = dfc['json_data'].map(get_html)) dfc
上述代码生成图6-7的输出。
图6-7
大多数行都成功抽取了页面的HTML内容,但是有一些却没有返回任何值。检查了空白的行之后,我们发现它们似乎主要来自BuzzFeed这个网站。这是合理的,因为这种页面主要是图片和小测验。这是一个小小的烦恼,我们不得不将就着用一下。
现在让我们取出HTML并将其转换为文本。这里将使用BeautifulSoup库为我们实现这个目标。
from bs4 import BeautifulSoup def text_from_html(x): try: soup = BeautifulSoup(x, 'lxml') return soup.get_text() except: return None dfc = dfc.assign(text = dfc['html'].map(text_from_html)) dfc
上述代码生成图6-8的输出。
图6-8
现在添加额外的特征。我们将添加页面上第一个图像中最突出的颜色。由embed.ly生成的JSON数据保存了每个图像的RGB值,这些值体现了对应图像的颜色,所以这将是一个简单的任务。
import matplotlib.colors as mpc def get_rgb(x): try: if x.get('images'): main_color = x.get('images')[0].get('colors') [0].get('color') return main_color except: return None def get_hex(x): try: if x.get('images'): main_color = x.get('images')[0].get('colors') [0].get('color') return mpc.rgb2hex([(x/255) for x in main_color]) except: return None dfc = dfc.assign(main_hex = dfc['json_data'].map(get_hex)) dfc = dfc.assign(main_rgb = dfc['json_data'].map(get_rgb)) dfc
上述代码生成图6-9的输出。
图6-9
我们提取出第一个图像中最突出的颜色并保存其RGB值,同时也将其转换为HEX十六进制值。稍后检查图像颜色的时候也会使用这个信息。
我们几乎完成了数据的处理部分,不过还需要转换一些从Ruzzit获取的数字。我们所拥有的分享次数是用于显示目的,而不是用于分析的格式,如图6-10所示。
图6-10
我们需要清理fb、lnkdn、pins和date(日期)`列,将它们从字符串表示转化为数字类型,如下所示。
def clean_counts(x): if 'M' in str(x): d = x.split('M')[0] dm = float(d) * 1000000 return dm elif 'k' in str(x): d = x.split('k')[0] dk = float(d.replace(',','')) * 1000 return dk elif ',' in str(x): d = x.replace(',','') return int(d) else: return x dfc = dfc.assign(fb = dfc['fb'].map(clean_counts)) dfc = dfc.assign(lnkdn = dfc['lnkdn'].map(clean_counts)) dfc = dfc.assign(pins = dfc['pins'].map(clean_counts)) dfc = dfc.assign(date = pd.to_datetime(dfc['date'], dayfirst=True)) dfc
上述代码生成图6-11的输出。
图6-11
最后,我们将添加最后一个特征,每列的字数统计。我们可以通过空格来切分文本,然后采取最终的计数。操作如下。
def get_word_count(x): if not x is None: return len(x.split(' ')) else: return None dfc = dfc.assign(word_count = dfc['text'].map(get_word_count)) dfc
上述代码生成图6-12的输出。
随着我们的数据准备就绪,现在可以开始进行分析了。我们将尝试寻找什么样的特征会使内容具有更高的传播度。
图6-12
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论