<Web Scraping with Python> Chapter 4 & 5

​Chapter 4 & 5: Using APIs & Storing Data

  • Parsing JSON
  • Storing Data to CSV
  • Integrating with Python & MySQL

1.Key:

Parsing JSON?

Python uses a more flexible approach and turns JSON objects into dictionaries, JSON arrays into lists, JSON strings into strings, and so forth. In this way, it makes it extremely easy to access and manipulate values stored in JSON.

Python 使用了一种更加灵活的方式来处理 JSON,把 JSON 转换成字典,JSON 数组转换成列表,JSON 字符串转换成 Python 字符串。通过这种方式,就可以让 JSON 的获取和操作变得更加简单。
下面的程序对维基百科的编辑历史页面里面的 IP 地址找出来,并查询 IP 地址所属的国家和地区:

from urllib.request import urlopen
from urllib.request import HTTPError
from bs4 import BeautifulSoup
import datetime
import json
import random
import re

random.seed(datetime.datetime.now())
def getLinks(article_url):
    html = urlopen("http://en.wikipedia.org"+article_url)
    bsObj = BeautifulSoup(html.read(),"html5lib")
    return bsObj.find("div",{"id":"bodyContent"}).findAll("a",href=re.compile("^(/wiki/)((?!:).)*$"))

def getHistoryIPs(page_url):
    # http://en.wikipedia.org/w/index.php?title=Title_in_URL&action=history
    page_url = page_url.replace("/wiki/","")
    history_url = "http://en.wikipedia.org/w/index.php?title=" + page_url + "&action=history"
    print("history url is: " + history_url)
    html = urlopen(history_url)
    bsObj = BeautifulSoup(html.read(),"html5lib")
    # finds only the links with class "mw-anonuserlink" which has IP addresses instead of usernames
    ipAddresses = bsObj.findAll("a", {"class":"mw-anonuserlink"})
    addressList = set()
    for ipAddresses in ipAddresses:
        addressList.add(ipAddresses.get_text())
    return addressList

def getCountry(ipAddress):
    try:
        response = urlopen("http://freegeoip.net/json/" + ipAddress).read().decode('utf-8')
    except HTTPError:
        return None
    responseJson = json.loads(response)
    return responseJson.get("country_code")

links = getLinks("/wiki/Python_(programming_language)")

while(len(links) > 0):
    for link in links:
        print("----------------")
        historyIPs = getHistoryIPs(link.attrs["href"])
        for historyIP in historyIPs:
            country = getCountry(historyIP)
            if country is not None:
                print(historyIP + "is from " + country)

    newLink = links[random.randint(0,len(links)-1)].attrs["href"]
    links = getLinks(newLink)

Download Page Source

下面的程序将 http://pythonscraping.com 主页上所有 src 属性的文件都下载下来,然后对 URL 链接进行清理和标准化,获得文件对绝对路径(而且去掉了外链)。最后,每个文件都会下载到程序所在文件夹到 downloaded 文件里:

from urllib.request import urlretrieve
from urllib.request import urlopen
from bs4 import BeautifulSoup
import os

download_directory = "downloaded"
base_url = "http://pythonscraping.com"

def getAbsolute_url(base_url,source):
    if source.startswith("http://www."):
        url = "http://" + source[11:]
    elif source.startswith("http://"):
        url = source
    elif source.startswith("www."):
        url = source[4:]
        url = "http://" + source
    else:
        url = base_url + "/" + source
    if base_url not in url:
        return None
    return url

def getDownloadPath(base_url, absolute_url, download_directory):
    path = absolute_url.replace("www","")
    path = path.replace(base_url,"")
    path = download_directory + path
    directory = os.path.dirname(path)

    if not os.path.exists(directory):
        os.makedirs(directory)

    return path

html = urlopen("http://www.pythonscraping.com")
bsObj = BeautifulSoup(html.read(),"html5lib")
downloadList = bsObj.findAll(src=True)

for download in downloadList:
    file_url = getAbsolute_url(base_url, download["src"])
    if file_url is not None:
        print(file_url)

urlretrieve(file_url,getDownloadPath(base_url,file_url,download_directory))

Storing Data to CSV

CSV, or comma-separated values, is one of the most popular file formats in which to store spreadsheet data. It is supported by Microsoft Excel and many other applica‐ tions because of its simplicity. The following is an example of a perfectly valid CSV file:

fruit,cost
apple,1.00
banana,0.30
pear,1.25

网络数据采集的一个常用功能就是获取 HTML 表格并写入 CSV 文件。


2.Need to Know:

MySQL

The download page

下载 .dmg 安装包,在 MySQL5.7.x 版本之后,安装的时候会随机分配一个初始密码!这非常重要,例如 root@localhost: ;,aLs&%%4ziE 密码很复杂,最好先复制下来,等会更改密码的时候需要用到。

安装完成之后,可以在系统偏好设置中看到多出了一个 MySQL,我们可以通过其来开关 MySQL 服务器,当然我们可以通过命令行输入来控制。
打开服务器,在命令行输入:

$ alias mysql=/usr/local/mysql/bin/mysql
$ alias mysqladmin=/usr/local/mysql/bin/mysqladmin

ps: 注意,这上面 alias 别名的方法,只是一次性的,意味着我们关闭了终端之后再开,命令行直接输入 mysql 或者 mysqladmin 就无效了。如果需要长期有效,需要修改文件,让终端启动的时候加载。

$ cd ~
$ vim ./bash_profile

如果你安装了 oh-my-zsh,去更改 .zshrc 文件。

然后更改密码,命令行输入:

$ mysqladmin - u root -p password xxx(我们需要的新密码)

确保 MySQL 服务器打开,然后命令输入:

$ mysql -u root -p

若未显示错误,则表示连接上数据库了


Integrating with Python

Python 没有内置的 MySQL 支持工具。不过,有很多开源的库可以用来与MySQL 做交互,Python2.x 和 Python3.x 版本都支持。最有名的一个库就是PyMySQL。

我是在 PyCharm 直接安装 PyMySQL,安装完成之后,如果我们的MySQL的服务器处于运行状态,应该就可以使用 PyMySQL 包。

>>> import pymysql.conn = pymysql.connect(host='127.0.0.1', unix_socket='/tmp/mysql.sock', 
            user='root', passwd='xxxx', db='mysql')
>>> cur = conn.cursor()
>>> cur.execute("USE scraping")
>>> cur.execute("SELECT * FROM pages WHERE id=1") 
>>> print(cur.fetchone())cur.close().conn.close() 
  1. 程序中有两个对象:连接对象 conn 和光标对象 cur
  2. 连接/光标模式是数据库编程中常见的模式。连接模式除了要连接数据库之外,还要发送数据库信息,处理回滚操作(当一个查询或一组查询被中断时,数据库需要回到初始状态,一般用事务控制手段实现状态会滚),创建新的光标对象,等等。
  3. 而一个 conn 可以有很多个 cur。一个光标跟踪一种状态信息,比如跟踪数据库的使用状态。如果你有多个数据库,且需要向所有数据库写内容,就需要多个光标来处理。光标还包含最后一次查询执行的结果。通过调用光标函数,比如 cur.fetchone(),可以获取查询结果。
  4. 用完光标和链接之后,千万记得要把它们关闭。如果不关闭就会导致连接泄漏(connection leak),造成一种未关闭连接的现象,即连接已经不在使用,但是数据库却不能关闭,因为数据库不能确定你还要不要继续使用它。这种现象会一直耗费数据库的资源,所以用完数据库之后记得关闭连接!
  5. 进行网络数据采集的时候,处理 Unicode 字符串是很痛苦的事情。默认情况下,MySQL 也不支持 Unicode 字符处理。不过我们可以设置这个功能,因为采集的时候,我们难免会遇到各种各样的字符,所以最好一开始就让我们的数据库支持 Unicode:
ALTER DATABASE scraping CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; 
ALTER TABLE pages CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 
ALTER TABLE pages CHANGE title title VARCHAR(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 
ALTER TABLE pages CHANGE content content VARCHAR(10000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 

我们尝试用下面的程序来存储数据:

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import datetime
import random
import pymysql

conn = pymysql.connect(host='127.0.0.1', unix_socket='/tmp/mysql.sock',
                       user='root',passwd='randolph',db='mysql',charset='utf8')

cur = conn.cursor()
cur.execute("USE scraping")

random.seed(datetime.datetime.now())

def store(title, content):
    cur.execute("INSERT INTO pages(title, content) VALUE (\"%s\",\"%s\")",(title,content))
    cur.connection.commit()

def getLinks(article_url):
    html = urlopen("http://en.wikipedia.org"+article_url)
    bsObj = BeautifulSoup(html.read(),"html5lib")
    title = bsObj.find("h1").get_text()
    content = bsObj.find("div", {"id":"mw-content-text"}).find("p").get_text()
    store(title,content)
    return bsObj.find("div",{"id":"bodyContent"}).findAll("a",href=re.compile("^(/wiki/)(?!:).)*$"))

links = getLinks("/wiki/Kevin_Bacon")

try:
    while len(links) > 0:
        newArticle = links[random.randint(0, len(links)-1)].attrs["href"]
        print(newArticle)
        links = getLinks(newArticle)

finally:
    cur.close()
    conn.close()
  • 需要注意的是 store 函数,它有两个参数:titlecontent,并把这两个参数加到了一个 INSERT 语句中并用光标执行,然后用光标进行连接确认。这是一个让光标与连接操作分离的好例子;当光标里存储了一些数据库与数据库上下文的信息时,需要通过连接的确认操作先将信息传进数据库,再将信息插入数据库。
  • 最后需要注意的是 finally 语句是在程序主循环的外面,代码的最底下。这样做可以保证,无论程序执行过程中如何发生中断或抛出异常(当然,因为网络很复杂,我们需要随时准备遭遇异常),光标和连接都会在程序结束前立即关闭。无论我们是在采集网络还是在处理一个打开连接的数据库,用 try...finally 都是一个好主意。

3.Correct errors in printing:

  • 暂无

4.Still have Question:

  • 暂无
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,440评论 6 428
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,590评论 18 139
  • GitHub 上有一个 Awesome - XXX 系列的资源整理,资源非常丰富,涉及面非常广。awesome-p...
    若与阅读 18,612评论 4 418
  • 环境管理管理Python版本和环境的工具。p–非常简单的交互式python版本管理工具。pyenv–简单的Pyth...
    MrHamster阅读 3,783评论 1 61
  • 【作者前言】:分享些本人工作中遇到的点点滴滴那些事儿,刚开始写博客,高手勿喷!以分享交流为主,欢迎各路豪杰点评改进...
    iOS_PM_WEB_尛鹏阅读 5,731评论 0 4