《数据科学实战之网络爬取》读书笔记

两种方式爬取Hacker News网页

Hacker News网页是国外一个很受欢迎的新闻聚合网站,计算机科学家、企业家、数据科学家对此很感兴趣

使用requests和Beautiful Soup爬取

使用requests和Beautiful Soup爬取是一般的做法吗,以下代码将使用一个简单的Python字典对象列表存储爬取的信息

import requests
import re
from bs4 import BeautifulSoup

articles = []

url = 'https://news.ycombinator.com/news'

r = requests.get(url)
html_soup = BeautifulSoup(r.text, 'html.parser')

for item in html_soup.find_all('tr', class_='athing'):
    item_a = item.find('a', class_='storylink')
    item_link = item_a.get('href') if item_a else None
    item_text = item_a.get_text(strip=True) if item_a else None
    next_row = item.find_next_sibling('tr')
    item_score = next_row.find('span', class_='score')
    item_score = item_score.get_text(strip=True) if item_score else '0 points'
    # We use regex here to find the correct element
    item_comments = next_row.find('a', text=re.compile('\d+( |\s)comment(s?)'))
    item_comments = item_comments.get_text(strip=True).replace('\xa0', ' ') \
                        if item_comments else '0 comments'
    articles.append({
        'link' : item_link,
        'title' : item_text,
        'score' : item_score,
        'comments' : item_comments})

for article in articles:
    print(article)

输出内容如下

使用api爬取

Hacker News有Api。提供结构化 、JSON格式的结果,下面是对上面示例代码的修改,使其不依赖于BS对html的解释

import requests

articles = []

url = 'https://hacker-news.firebaseio.com/v0'

top_stories = requests.get(url + '/topstories.json').json()

for story_id in top_stories:
    story_url = url + '/item/{}.json'.format(story_id)
    print('Fetching:', story_url)
    r = requests.get(story_url)
    story_dict = r.json()
    articles.append(story_dict)

for article in articles:
    print(article)

爬取书籍信息

使用requests和bs爬http://books.toscrape.com/上的书籍信息,网页显示内容如下

在这里插入图片描述

对于每本书,都需要获得

  • 标题
  • 封面
  • 价格和库存情况
  • 评级
  • 产品说明
  • 其他产品信息

然后使用dataset库将此信息存储在sqlite数据库中(以使用更新的方式编写程序,这样就可以在多次运行程序的情况下不会在数据库中插入重复记录)

核心代码如下

if __name__ == "__main__":
    # Scrape the pages in the catalogue
    url = base_url
    inp = input('Do you wish to re-scrape the catalogue (y/n)? ')
    while True and inp == 'y':
        # 访问base_url拿到页面的链接
        scrape_books(html_soup, url) # 分析url,拿到页面中的图书名和链接,存入到db数据库对应的表中
        # Is there a next page?
        next_a = html_soup.select('li.next > a') # 获取下一页
        if not next_a or not next_a[0].get('href'):
            break
        url = urljoin(url, next_a[0].get('href'))

    # Now scrape book by book, oldest first
    books = db['books'].find(order_by=['last_seen']) # 从db中取出对应的图书,里面包含图书名和链接
    for book in books:
        # 访问图书具体的链接
        scrape_book(html_soup, book_id) # 从图书链接中拿到图书的信息,存放到数据库db['books_info']中
        # Update the last seen timestamp
        db['books'].upsert({'book_id': book_id,
                            'last_seen': datetime.now()
                            }, ['book_id'])

爬取GitHub上项目被收藏的次数

咱们要爬取的链接最终地址为:https://github.com/Macuyiko?page=1&tab=repositories

如果是企业用户,则为:https://github.com/google?page=1&tab=repositories

要获取的信息,主要是项目名和编程语言、star个数

在这里插入图片描述

在这里插入图片描述

代码如下

#!/usr/bin/env python
# encoding: utf-8

import requests
from bs4 import BeautifulSoup
import re

session = requests.Session()

url = 'https://github.com/{}'
username = 'Macuyiko'

if __name__ == "__main__":
    r = session.get(url.format(username), params={'page': 1, 'tab': 'repositories'}, verify=False)
    html_soup = BeautifulSoup(r.text, 'html.parser')
    is_normal_user = False
    repos_element = html_soup.find(class_='repo-list') # 企业用户才会有repo-list,非企业用户为user-repositories-list
    if not repos_element:
        is_normal_user = True
        repos_element = html_soup.find(id='user-repositories-list')

    repos = repos_element.find_all('li')
    for repo in repos:
        name = repo.find('h3').find('a').get_text(strip=True) # 找到元素下的h3标签下的a标签,取出其text的内容
        language = repo.find(attrs={'itemprop': 'programmingLanguage'}) # 找到itemprop属性等于programmingLanguage的标签
        language = language.get_text(strip=True) if language else 'unknown'
        stars = repo.find('a', attrs={'href': re.compile('\/stargazers')})
        stars = int(stars.get_text(strip=True).replace(',', '')) if stars else 0
        print(name, language, stars)

爬取和分析网络论坛的互动

爬取网络论坛的互动信息

爬取的目标网站内容如下,分为两个,第一个是主页的帖子列表

在这里插入图片描述

第二个为帖子中的回复信息,只取出对应的用户,不关注发表的内容

在这里插入图片描述

所以获取的核心就是首先拿到所有的标签信息,然后通过bs或者xpath来匹配,可以多拿几页数据

爬取的内容主要为评论和回复

在这里插入图片描述

对应的帖子内容为

在这里插入图片描述

分析网络论坛的互动信息

核心代码如下

heatmap = plt.pcolor(df, cmap='Blues')
y_vals = np.arange(0.5, len(df.index), 1)
x_vals = np.arange(0.5, len(df.columns), 1)
plt.yticks(y_vals, df.index)
plt.xticks(x_vals, df.columns, rotation='vertical')
for y in range(len(df.index)):
    for x in range(len(df.columns)):
        if df.iloc[y, x] == 0:
            continue
        plt.text(x + 0.5, y + 0.5, '%.0f' % df.iloc[y, x],
                 horizontalalignment='center',
                 verticalalignment='center')
plt.savefig("1.jpg")
plt.show()

整个部分的代码,posts此时保存的内容,每一个listitem为一个帖子,帖子的内容大概分为两种,非引用的(‘bluefish’, []);引用的(‘almostthere’, [‘kayman’])

在这个部分,需要把posts的内容进行切分,保存成此类型:‘zeke’: {‘bluefish’: 1, ‘almostthere’: 4}
所表单的意思为,zeke引用了两个人,分别为bluefish何almostthere,bluefish引用了一次,almostthere引用了4次;将切分处理后的内容保存到users中,内容处理完成后,大概如下

{'zeke': {'bluefish': 1, 'almostthere': 1}, 'trinity': {'almostthere': 1}, 'paula53': {'almostthere': 1}, 'toejam': {'almostthere': 1, 'Ohm': 1}, 'stickman': {'almostthere': 1}, 'tamtrails': {'almostthere': 1}, 'almostthere': {'tamtrails': 1, 'kayman': 1}, 'kayman': {'almostthere': 1}, 'lanceman': {'almostthere': 1}, 'pollock': {'almostthere': 1}, 'mitsmit': {'almostthere': 1}, 'Christian': {'almostthere': 1}, 'softskull': {'almostthere': 1}, 'argus': {'almostthere': 1}, 'lyssa7': {'almostthere': 1}, 'kevin': {'almostthere': 1}, 'dogrescuer': {'almostthere': 1}, 'RedDoug': {'kayman': 1}, 'Richard': {'almostthere': 1}, 'rebeccad': {'almostthere': 1, 'rangewalker': 2, 'Ohm': 1}, 'assen': {'almostthere': 1}, 'james2020': {'almostthere': 1}, 'gabby': {'rangewalker': 2, 'reuben': 1}, 'texasbb': {'rangewalker': 1}, 'High Sierra Fan': {'rangewalker': 1}, 'Ohm': {'rangewalker': 2, 'Lamebeaver': 1, 'reuben': 1, 'cheaptentguy': 1}, 'Lamebeaver': {'rangewalker': 1}, 'reuben': {'rangewalker': 2, 'Ohm': 1}, 'cheaptentguy': {'rangewalker': 1}}

将users转成pd.DataFrame的二维数组类型,那么index就是字典的key,columns就是value;再按照这个二维数组来遍历值然后输出到plt中,最后看到输出的图像如下

在这里插入图片描述

收集和聚类时尚数据集

这个例子中,使用Zalando(一个瑞典网上商店)来获取时尚产品的图片集合,并使用t-SNE对他们进行聚类

获取图片素材

网站的地址如下:https://www.zalando.co.uk/womens-clothing-dresses/

只需要把15页的图片下载下来做测试即可,代码可参见附件

对图片进行聚类分析

t-SNE的原理和推导如下:t-SNE原理与推导,特别适用于高维数据集(如图片)的可视化

用imread加载图片的时候,可能会遇到scipy.misc报错的问题

from scipy.misc import imread报错:ImportError: cannot import name imread

两种方法解决

  • 将scipy降级到1.2.1版本(pip install scipy==1.2.1)
  • 使用imageio.imread代替imread读取图片
  • 使用pillow来读取图片

t-SNE代码执行的效果如图(只选择了20张来做测试):

在这里插入图片描述

Amazon评论的情感分析

获取评论数据

从Amazon网页中,比如

https://www.amazon.com/product-reviews/1449355730

从chrome分析,获取所有当前页评论的结果是post方式,提交的字段为:

在这里插入图片描述

拼接字段,上传到path中就可以拿到当前页的评论数据了,response的数据需要自己做解析

在这里插入图片描述

做评论的情感分析

对每次评论的情感进行评分,需要使用到vaderSentiment库,安装使用

pip install -U vaderSentiment

此时还需要用到nltk库,安装为

pip install -U nltk

对于单个句子,使用vaderSentiment库非常简单,如下是使用vaderSentiment库的测试代码

#!/usr/bin/env python
# encoding: utf-8

from nltk.sentiment.vader import SentimentIntensityAnalyzer
# import nltk
# nltk.download('vader_lexicon') # 只需要执行一次

if __name__ == "__main__":
    analyzer = SentimentIntensityAnalyzer()
    sentence = "I'm really happy with my pyrchase"
    vs = analyzer.polarity_scores(sentence)
    print(vs) # {'neg': 0.0, 'neu': 0.556, 'pos': 0.444, 'compound': 0.6115}

对于较长文本的情感分析,一种简单的方法是计算每个句子的情感得分,并将其平均到文本中的所有句子中,示例如下

#!/usr/bin/env python
# encoding: utf-8

from nltk.sentiment.vader import SentimentIntensityAnalyzer
# import nltk
# nltk.download('vader_lexicon') # 只需要执行一次,分析单个句子时候需要用到
# import nltk
# nltk.download('punkt') # 只需要执行一次,分析多个句子时候需要用到
from nltk import tokenize

if __name__ == "__main__":
    analyzer = SentimentIntensityAnalyzer()
    paragraph = """
        I'm really happy with my pyrchase.
        I've been using the producy for two weeks now.
        It does exactly as described in the product description.
        The only problem is that it takes a long time to charge.
        However, since I recharge during nights,this is something I can live with.
    """

    sentence_list = tokenize.sent_tokenize(paragraph)
    cumulative_sentiment = 0.0
    for sentence in sentence_list:
        vs = analyzer.polarity_scores(sentence)
        cumulative_sentiment += vs["compound"]
        print(sentence, " : ", vs["compound"])

    average_score = cumulative_sentiment / len(sentence_list)
    print("Average score:", average_score)

输入如下

I'm really happy with my pyrchase.  :  0.6115
I've been using the producy for two weeks now.  :  0.0
It does exactly as described in the product description.  :  0.0
The only problem is that it takes a long time to charge.  :  -0.4019
However, since I recharge during nights,this is something I can live with.  :  0.0
Average score: 0.04192000000000001

如果遇到资源错误,又无法下载punkt的,请参阅参考链接中的“nltk.download(‘punkt’) False”

把这样的思路应用到上面获取到的Amazon评论当中,很容易得到每一个评论的情感,得到每一个评论的情感后再按照评价的星级进行归类,就可以得到每个星级的情感平均分了。matplotlib中的小提琴图,很容易能看到这些分布

在这里插入图片描述

爬取和分析维基百科关联图

数据获取

需要使用到两个数据库表

  • 一个为pages:记录访问过的URL列表机器页面标题
  • 另一个为links,仅仅包含一对url来表示页面之间的链接

爬取的时候使用joblib库来进行多线程

比较有意思的可能是这个页面发现链接了,使用了joblib模块,在模块的线程回调函数中执行get_title_and_links函数,该函数获取当前html的标题和当前url链接、页面发现的链接,然后到结果scraped_results中;一轮结束后外层函数遍历这个scraped_results,把当前html的标题和当前url链接放到store_page中处理,页面发现的链接在store_links中处理;

下一次的循环通过get_random_unvisited_pages返回,get_random_unvisited_pages主要完成从所以发现的链接中获取未访问的,main部分的代码如下

if __name__ == '__main__':
    urls_to_visit = [base_url]
    while urls_to_visit:
        scraped_results = Parallel(n_jobs=5, backend="threading")(
            delayed(get_title_and_links)(base_url, url) for url in urls_to_visit
        )
        for url, page_title, links in scraped_results:
            store_page(url, page_title)
            store_links(url, links)
        urls_to_visit = get_random_unvisited_pages()

绘制关联图

有个爬取的数据,就可以使用NetworkX来可视化图了

在这里插入图片描述

有关NetworkX的使用不是重点,需要进一步了解的话,可以查看这个:networkx整理

爬取和可视化董事会成员图

爬取需要的数据

从网页https://de.reuters.com/finance/markets/index/.SPX中获取表格中的公司别名

在这里插入图片描述
得到别名后,拼接进链接中

officers = 'https://www.reuters.com/companies/{symbol}/people'

在链接中拿到对应公司的信息

在这里插入图片描述

然后存储到pandas中,并持久化到pickle中

可视化数据

使用NetworkX来简单地解析收集到的信息,并导出一种可以用Gephi(一个流行的图形可视化工具)读取的格式的图形,此工具可以从https://gephi.org/users/download/下载

不过比较失败的是,我没能按照书中的过滤得到最后的图

在这里插入图片描述
我的效果得到的是这个,还不清楚如何配置过滤选项
在这里插入图片描述

这是的用Gephi软件打开的.gexf文件

在这里插入图片描述

使用深度学习破解验证码图片

构建训练集

首先需要安装一些用到的模块

pip install -U captcha
pip install -U numpy
pip install -U opencv-python

生成4个字母长度的训练集

constants.py包含了一些变量

CAPTCHA_FOLDER = 'generated_images'
LETTERS_FOLDER = 'letters'

CHARACTERS = list('QWERTPASDFGHKLZXBNM')
NR_CAPTCHAS = 1000
NR_CHARACTERS = 4

MODEL_FILE = 'model.hdf5'
LABELS_FILE = 'labels.dat'

MODEL_SHAPE = (100, 100)

generate.py主要负责生成验证码

from random import choice
from captcha.image import ImageCaptcha
import os.path
from os import makedirs
from .constants import *

makedirs(CAPTCHA_FOLDER)

image = ImageCaptcha()

for i in range(NR_CAPTCHAS):
    captcha = ''.join([choice(CHARACTERS) for c in range(NR_CHARACTERS)])
    filename = os.path.join(CAPTCHA_FOLDER, '{}_{}.png'.format(captcha, i))
    image.write(captcha, filename)
    print('Generated:', captcha)

运行之后,可以看到在文件夹generated_images下出现如下的验证码

在这里插入图片描述

将图像分割成单独的部分,尝试构建模型

接下来的操作是把验证码图像分割成单独的部分,每个部分一个字符。使用opencv的对生成的图像进行阈值处理、开操作和轮廓检测。下面的代码主要做如下几个操作

  • 将图片二值化后进行形态学操作,过滤掉噪声
  • 用opencv的findContours方法提取链接的白色像素部分
  • 调用drawContours来绘制发现的部分
  • 将提取出来的轮廓按照文件夹名,分别存放到不同的文件夹中

下面是一个测试脚本,完成的功能就是提取一个字母,完成的操作如下

  • 将原图像进行去噪得到图像1
  • 创一个新的黑色图像,大小与原始图像一致
  • 取出来一个轮廓,并用白色绘制出来
  • 将图像1和蒙版按位and操作组合得到字母
import cv2
import numpy as np

# Change this to one of your generated images:
image_file = 'example.png'

image = cv2.imread(image_file)
cv2.imshow('Original image', image)

# Convert to grayscale, followed by thresholding to black and white
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
cv2.imshow('Black and white', thresh)

# Apply opening: "erosion" followed by "dilation"
denoised = thresh.copy()
kernel = np.ones((4, 3), np.uint8)
denoised = cv2.erode(denoised, kernel, iterations=1)
kernel = np.ones((6, 3), np.uint8)
denoised = cv2.dilate(denoised, kernel, iterations=1)
cv2.imshow('Denoised', denoised)

# Now find contours and overlay them over our original image
_, cnts, _ = cv2.findContours(denoised.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
contoured = image.copy()
cv2.drawContours(contoured, cnts, contourIdx=-1, color=(255, 0, 0), thickness=-1)
cv2.imshow('Contours', contoured)

# Create a fresh 'mask' image
mask = np.ones((image.shape[0], image.shape[1]), dtype="uint8") * 0
# We'll use the first contour as an example
contour = cnts[0]
# Draw this contour over the mask
cv2.drawContours(mask, [contour], -1, (255, 255, 255), -1)

cv2.imshow('Denoised image', denoised)
cv2.imshow('Mask after drawing contour', mask)

result = cv2.bitwise_and(denoised, mask)
cv2.imshow('Result after and operation', result)

retain = result > 0
result = result[np.ix_(retain.any(1), retain.any(0))]
cv2.imshow('Final result', result)

cv2.waitKey(0)

代码运行后得到的效果如下

在这里插入图片描述

如果按轮廓从左取到后,可以得到验证码的整个字符,但仍然需要考虑字符重叠;书中按照从最左边的白色像素到最右边的白色像素的距离除以期望看到的字符数(4)获取估计的宽度,如果轮廓比预期的要宽,将它切割成m个相等的部分,m等于轮廓的宽度除以预期的宽度

将思路封装到functions.py中,那么得到所有训练集的字符,只需要运行cut.py代码即可

在这里插入图片描述

使用深度学习框架

安装keras

安装tensorflow

pip install -U 其实安装tensorflow==1.9.0

安装配套版本的keras

pip install -U keras==2.2.0

keras和TensorFlow的版本需要匹配,当前我的tf版本是1.9.0,因此需要使用keras为2.2.0的版本,版本对应请参考:
https://docs.floydhub.com/guides/environments/

通过字符集来训练模型

要做的事情仅仅为

  • 循环遍历创建的索引字符图像,调整他们的大小并存储他们的像素矩阵结果
  • 数据进行规范化,使每个值都位于0~1之间
  • 对字符进行二值化处理,每个标签都转换为输出定点,每个索引对应一个可能的字符,其值设置为1或者0,使类似Q的字母变成[1,0,0,0,…]
  • 保存上面的转换,因为后期在模型的应用过程中还需要对字符进行逆转换
  • 构建神经架构,开始训练模型

运行train.py代码后得到的输出如下

在这里插入图片描述

最终的两个输出文件为labels.dat(标签信息)和model.hdf5(模型)

测试识别结果

使用或者重新用一开始的代码生成一个新的验证码,放到test_images文件夹下作为测试

在这里插入图片描述

执行apply.py代码,可以看到如下结果,对于219中的图,比较难识别出来的HL还说得过去,可是连D都能识别成了A

在这里插入图片描述

全文所涉及的代码下载地址

https://download.csdn.net/download/zengraoli/12342255

参考链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值