Python網絡數據采集3-數據存到CSV以及MySql

Python網絡數據采集3-數據存到CSV以及MySql

先熱熱身,下載某個頁面的所有圖片。

import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
                  ' Chrome/52.0.2743.116 Safari/537.36 Edge/15.16193'}

start_url = 'https://www.pythonscraping.com'

r = requests.get(start_url, headers=headers)
soup = BeautifulSoup(r.text, 'lxml')
# 獲取所有img標簽
img_tags = soup.find_all('img')
for tag in img_tags:
    print(tag['src'])
https://www.pythonscraping.com/sites/default/files/lrg_0.jpg
http://pythonscraping.com/img/lrg%20(1).jpg

將網頁表格存儲到CSV文件中

以這個網址為例,有好幾個表格,我們對第一個表格進行爬取。Wiki-各種編輯器的比較

import csv
import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
                  ' Chrome/52.0.2743.116 Safari/537.36 Edge/15.16193'}

url = 'https://en.wikipedia.org/wiki/Comparison_of_text_editors'
r = requests.get(url, headers=headers)
soup = BeautifulSoup(r.text, 'lxml')
# 只要第一個表格
rows = soup.find('table', class_='wikitable').find_all('tr')

# csv寫入時候每寫一行會有一空行被寫入,所以設置newline為空
with open('editors.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.writer(f)
    for row in rows:
        csv_row = []
        for cell in row.find_all(['th', 'td']):
            csv_row.append(cell.text)

        writer.writerow(csv_row)

需要注意的有一點,打開文件的時候需要指定newline='',因為寫入csv文件時,每寫入一行就會有一空行被寫入。

從網絡讀取CSV文件

上面介紹了將網頁內容存到CSV文件中。如果是從網上獲取到了CSV文件呢?我們不希望下載后再從本地讀取。但是網絡請求的話,返回的是字符串而非文件對象。csv.reader()需要傳入一個文件對象。故需要將獲取到的字符串轉換成文件對象。Python的內置庫,StringIO和BytesIO可以將字符串/字節當作文件一樣來處理。對于csv模塊,要求reader迭代器返回字符串類型,所以使用StringIO,如果處理二進制數據,則用BytesIO。轉換為文件對象,就能用CSV模塊處理了。

下面的代碼最為關鍵的就是data_file = StringIO(csv_data.text)將字符串轉換為類似文件的對象。

from io import StringIO
import csv
import requests

csv_data = requests.get('http://pythonscraping.com/files/MontyPythonAlbums.csv')
data_file = StringIO(csv_data.text)
reader = csv.reader(data_file)

for row in reader:
    print(row)

['Name', 'Year']
["Monty Python's Flying Circus", '1970']
['Another Monty Python Record', '1971']
["Monty Python's Previous Record", '1972']
['The Monty Python Matching Tie and Handkerchief', '1973']
['Monty Python Live at Drury Lane', '1974']
['An Album of the Soundtrack of the Trailer of the Film of Monty Python and the Holy Grail', '1975']
['Monty Python Live at City Center', '1977']
['The Monty Python Instant Record Collection', '1977']
["Monty Python's Life of Brian", '1979']
["Monty Python's Cotractual Obligation Album", '1980']
["Monty Python's The Meaning of Life", '1983']
['The Final Rip Off', '1987']
['Monty Python Sings', '1989']
['The Ultimate Monty Python Rip Off', '1994']
['Monty Python Sings Again', '2014']

DictReader可以像操作字典那樣獲取數據,把表的第一行(一般是標頭)作為key。可訪問每一行中那個某個key對應的數據。
每一行數據都是OrderDict,使用Key可訪問。看上面打印信息的第一行,說明由NameYear兩個Key。也可以使用reader.fieldnames查看。

from io import StringIO
import csv
import requests

csv_data = requests.get('http://pythonscraping.com/files/MontyPythonAlbums.csv')
data_file = StringIO(csv_data.text)
reader = csv.DictReader(data_file)
# 查看Key
print(reader.fieldnames)

for row in reader:
    print(row['Year'], row['Name'], sep=': ')

['Name', 'Year']
1970: Monty Python's Flying Circus
1971: Another Monty Python Record
1972: Monty Python's Previous Record
1973: The Monty Python Matching Tie and Handkerchief
1974: Monty Python Live at Drury Lane
1975: An Album of the Soundtrack of the Trailer of the Film of Monty Python and the Holy Grail
1977: Monty Python Live at City Center
1977: The Monty Python Instant Record Collection
1979: Monty Python's Life of Brian
1980: Monty Python's Cotractual Obligation Album
1983: Monty Python's The Meaning of Life
1987: The Final Rip Off
1989: Monty Python Sings
1994: The Ultimate Monty Python Rip Off
2014: Monty Python Sings Again

寫入數據庫中

數據庫使用MySql

如果服務沒有后啟動,首先啟動服務。net start mysql57這里57是版本號,根據自己的版本填寫。

然后mysql -u root -p輸入密碼后就可以使用了。

先來簡單復習下SQL語法。

SQL基本語法

下面是關于數據庫的操作

  • create database example;這樣創建一個叫做example的數據庫。
  • drop database example;則是刪除這個數據庫。
  • show databases;可以查看所有數據庫。
  • use example;使用這個數據庫。select database();顯示當前正在使用的數據庫。

下面是關于表的操作

  • show tables;查看當前數據庫下的所有表。
  • desc some_table;查看某個表的具體結構。
  • drop table some_table;刪除某個表。
  • alter table some_table add age int;加一列
  • alter table some_table drop age;刪除一列

下面是表的CURD

  • insert into t_user(name, email) values('tom','tom@163.com');添加一行數據,可以指定任意列的內容,剩下的要么自己生成(如id一般自增),要么就是默認值。
  • UPDATE t_user SET NAME='rose' WHERE id=7;更新數據,表示將id為7的數據name改為rose。
  • DELETE FROM t_user WHERE NAME='God';把name是God的記錄刪除。DELETE FROM t_user;刪除整張表中所有記錄.
  • select * from stu;查詢stu表里所有數據,*是通配符匹配所有。
  • select sname from stu;查詢stu的sname那列。
  • select * from stu where gender='female' and age<50;條件查詢。

使用pymysql連接到MySql

Python連接MySql,這里使用pymysql

import pymysql

conn = pymysql.connect(host='localhost', user='root', password='admin', db='example',charset='utf8')

cur = conn.cursor()

try:
    # 上面填了參數這句就不是必須的
    # cur.execute('USE example')
    cur.execute('SELECT * FROM pages')
    print(cur.fetchone())
finally:
    cur.close()
    conn.close()
(1, 'Test Title', '方法', datetime.datetime(2017, 7, 15, 15, 45, 46))

db參數表示選擇的數據庫名稱。連接數據庫時候,加上charset=utf8可以處理中文字符。注意不要寫成utf-8。然后就是連接和光標都要記得close。

接下來從某個wiki頁面開始,隨機獲取一個詞條訪問其頁面,并儲存詞條的標題(title)和正文第一段(content)到MySql。

建表。

create TABLE pages(id int primary key auto_increment,title varchar(200),content varchar(10000),created timestamp default current_timestamp);

上代碼

import re
import random

import pymysql
import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
                  ' Chrome/52.0.2743.116 Safari/537.36 Edge/15.16193'}

conn = pymysql.connect(host='localhost', user='root', password='admin', db='example', charset='utf8')
cur = conn.cursor()


# 存到數據庫
def store(title, content):
    try:
        cur.execute(f"INSERT INTO pages(title, content) VALUES('{title}', '{content}');")
    except Exception as e:
        print(e)
    else:
        conn.commit()


# 獲得頁面內所有詞條的鏈接
def get_links(article_url):
    r = requests.get('https://en.wikipedia.org' + article_url, headers=headers)
    soup = BeautifulSoup(r.text, 'lxml')
    title = soup.h1.string
    content = soup.find('div', id='mw-content-text').find('p').text
    store(title, content)
    links = soup.find('div', id='bodyContent').find_all('a', href=re.compile('^/wiki/[^:/]*$'))
    return links


link_list = get_links('/wiki/Kevin_Bacon')
try:
    while len(link_list) > 0:
        new_article = random.choice(link_list).get('href')
        print(new_article)
        link_list = get_links(new_article)
finally:
    cur.close()
    conn.close()

conn.commit()注意這句,由于連接不是自動提交的,需要我們手動提交,確保數據確實改變。有些詞條的可能會導致在執行查詢語句的時候發生異常,處理一下,不讓其終止爬取??聪陆Y果。

保存鏈接之間的聯系

比如鏈接A,能夠在這個頁面里找到鏈接B。則可以表示為A -> B。我們就是要保存這種聯系到數據庫。先建表:

pages表只保存鏈接url。

CREATE TABLE `pages` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) DEFAULT NULL,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
);

links表保存鏈接的fromId和toId,這兩個id和pages里面的id是一致的。如1 -> 2就是pages里id為1的url頁面里可以訪問到id為2的url的意思。

CREATE TABLE `links` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `fromId` int(11) DEFAULT NULL,
  `toId` int(11) DEFAULT NULL,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`);

上面的建表語句看起來有點臃腫,我是先用可視化工具建表后,再用show create table pages這樣的語句查看的。

import re

import pymysql
import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
                  ' Chrome/52.0.2743.116 Safari/537.36 Edge/15.16193'}

conn = pymysql.connect(host='localhost', user='root', password='admin', db='wiki', charset='utf8')
cur = conn.cursor()


def insert_page_if_not_exists(url):
    cur.execute(f"SELECT * FROM pages WHERE url='{url}';")
    # 這條url沒有插入的話
    if cur.rowcount == 0:
        # 那就插入
        cur.execute(f"INSERT INTO pages(url) VALUES('{url}');")
        conn.commit()
        # 剛插入數據的id
        return cur.lastrowid
    # 否則已經存在這條數據,因為url一般是唯一的,所以獲取一個就行,取腳標0是獲得id
    else:
        return cur.fetchone()[0]


def insert_link(from_page, to_page):
    print(from_page, ' -> ', to_page)
    cur.execute(f"SELECT * FROM links WHERE fromId={from_page} AND toId={to_page};")
    # 如果查詢不到數據,則插入,插入需要兩個pages的id,即兩個url
    if cur.rowcount == 0:
        cur.execute(f"INSERT INTO links(fromId, toId) VALUES({from_page}, {to_page});")
        conn.commit()


# 鏈接去重
pages = set()


# 得到所有鏈接
def get_links(page_url, recursion_level):
    global pages
    if recursion_level == 0:
        return
    # 這是剛插入的鏈接
    page_id = insert_page_if_not_exists(page_url)
    r = requests.get('https://en.wikipedia.org' + page_url, headers=headers)
    soup = BeautifulSoup(r.text, 'lxml')
    link_tags = soup.find_all('a', href=re.compile('^/wiki/[^:/]*$'))
    for link_tag in link_tags:
        # page_id是剛插入的url,參數里再次調用了insert_page...方法,獲得了剛插入的url里能去往的url列表
        # 由此形成聯系,比如剛插入的id為1,id為1的url里能去往的id有2、3、4...,則形成1 -> 2, 1 -> 3這樣的聯系
        insert_link(page_id, insert_page_if_not_exists(link_tag['href']))

        if link_tag['href'] not in pages:
            new_page = link_tag['href']
            pages.add(new_page)
            # 遞歸查找, 只能遞歸recursion_level次
            get_links(new_page, recursion_level - 1)


if __name__ == '__main__':
    try:
        get_links('/wiki/Kevin_Bacon', 5)
    except Exception as e:
        print(e)
    finally:
        cur.close()
        conn.close()

1  ->  2
2  ->  1
1  ->  2
1  ->  3
3  ->  4
4  ->  5
4  ->  6
4  ->  7
4  ->  8
4  ->  4
4  ->  4
4  ->  9
4  ->  9
3  ->  10
10  ->  11
10  ->  12
10  ->  13
10  ->  14
10  ->  15
10  ->  16
10  ->  17
10  ->  18
10  ->  19
10  ->  20
10  ->  21
...

看打印的信息,一目了然??辞皟尚写蛴?,pages表里id為1的url可以訪問id為2的url,同時pages表里id為2的url可以訪問id為1的url...依次類推。

首先需要使用insert_page_if_not_exists(page_url)獲得鏈接的id,然后使用insert_link(fromId, toId)形成聯系。fromId是當前頁面的url,toId則是從當前頁面能夠去往的url的id,這些能去往的url用bs4找到以列表形式返回。當前所處的url即page_id,所以需要在insert_link的第二個參數中,再次調用insert_page_if_not_exists(link)以獲得列表中每個url的id。由此形成了聯系。比如剛插入的id為1,id為1的url里能去往的id有2、3、4...,則形成1 -> 2, 1 -> 3這樣的聯系。

看下數據庫。下面是pages表,每一個id都對應一個url。

然后下面是links表,fromIdtoId就是pages中的id。當然和打印的數據是一樣的咯,不過打印了看看就過去了,存下來的話哪天需要分析這些數據就大有用處了。


by @sunhaiyu

2017.7.15

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • MySQL 數據庫常用命令 1、MySQL常用命令 create database name; 創建數據庫 use...
    55lover閱讀 4,820評論 1 57
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,737評論 18 399
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,831評論 0 11
  • 1.MySQL是一個關系型數據庫管理系統,由瑞典MySQL AB 公司開發,目前屬于 Oracle 旗下產品。My...
    黃花菜已涼閱讀 4,594評論 3 60
  • 放下其實并沒有我想的那么可怕,因為這一段失去的戀情,我讓自己疼了4個月,吃不下,睡不著,日想夜想,甚至每一晚睡覺心...
    張艾婷閱讀 349評論 1 2