爬虫笔记(正则与Beautiful Soup对比实现)

爬虫概述

通俗的讲,爬虫就是模拟浏览器,向服务器发出请求,获取到服务器返回的内容,再挑出我们想要的内容保存下来。所以,写爬虫主要分为三步:
1.发出请求
2.解析页面
3.保存数据

一、发出请求

最基础的HTTP库有urllib,reuests
首先介绍urllib

1.1 urllib的使用

urllib主要有四个模块,request,error,parse,robotparser

  • request : 最基本的HTTP请求模块,可用来模拟发出请。想在浏览器中输入网址,然后回车一样,只需要给库方法传递url以及额外的参数即可。
  • error : 异常处理模块,如果出现请求错误,我们可以捕获这些异常

urlopen()

urlopen()参数为url,模拟打开浏览器,下面我们看下使用方法:

import urllib.request
response = urllib.request.urlopen("https://www.python.org")
print(response.read().decode('utf-8'))

返回的是Python 官网的网页源代码:

<div id="touchnav-wrapper">

    <div id="nojs" class="do-not-print">
        <p><strong>Notice:</strong> While Javascript is not essential for this website, your interaction with the content will be limited. Please turn Javascript on for the full experience. </p>
    </div>

接下来,我们看下返回的结果是什么类型:

print(type(response))

可以看到返回的结果是HTTPResponse 对象:

<class 'http.client.HTTPResponse'>

所以,response就拥有read() , readinto() , getheader(name) , getheaders() 等方法以及 msg , status , reason , 的属性。

Request类

如果需要传入更多的参数,就需要使用Request类
看下Request类的构造:

class urllib.request.Request(url,data=None,headers={},origin_req_host=None, unverifiable=False , method= None)
  • data : data参数如果要传,需要传入bytes(字节流),如果是字典,可以先用urllib.parse模块里的urlencode()编码
  • headers : headers是一个字典,即请求头。
    下面用一个例子来传入多个参数:
from urllib import request,parse

url = 'http://httpbin.org/post'
headers = {
  'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
}
dict = {
  'name':'Mike'
}
data = bytes(parse.urlencode(dict),encoding='utf-8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

结果为:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "name": "Mike"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Connection": "close",
    "Content-Length": "9",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
  },
  "json": null,
  "origin": "218.94.83.134",
  "url": "http://httpbin.org/post"
}

1.2 requests的使用

与urlopen 类似,request.get()方法,也是向浏览器发起请求。

import requests
response = requests.get('https://www.python.org')
print(type(response))
print(response)

返回结果为:

<class 'requests.models.Response'>
<Response [200]>

由此可见,response 是浏览器的Response,可用response.text或者response.content获取网站的内容。前者为str类型,后者为bytes类型。
同样我们可以传入headers:

import requests
headers = {
  'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36'
}
response = requests.get('https://www.python.org',headers=headers)

如果请求是post方式,直接传入data即可:

from urllib import request,parse
url = 'http://httpbin.org/post'
data = {
  'name':'Mike'
}
data = bytes(parse.urlencode(dict),encoding='utf-8')

response = requests.get(req)
print(response.text)

注意:两种发起请求方法都可以,两种方法获取信息不一样,urlopen()返回源码使用response.read()方法,requests.get()使用response.text。我一般常用requests,get()方法向浏览器发起请求。

二、解析页面

当我们获取到服务器发送回的响应后,我们便通过解析源码来获取到我们想要的内容。解析页面,我们常用的方法有:

  • 正则表达式
  • XPath
  • Beautiful Soup

2.1 正则表达式

常用的正则表达式规则:

模式 描述
\w 匹配字母、数字、及下划线
\s 匹配任意空白字符
\t 匹配一个换行符
\d 匹配任意数字,等价于[0-9]
. 匹配任意字符
* 匹配0个或者多个表达式
匹配0个或者1个前面的正则表达式定一的片段,非贪婪模式
a\b (中间是竖线) 匹配a或者b
() 括号内的表达式,也表示一个组

常用的方法有:

  • match() : 从字符串的开头开始匹配,(不常用)两个参数,第一个传入正则,第二个传入待匹配字符串,返回结果是SRE_Match对象,有两个属性,group()和span(),前者输出匹配到的内容,后者输出匹配的范围。
  • search() : 匹配时,扫描整个字符串,返回匹配的第一个内容。
  • findall() : 扫描全部字符串,返回所有内容。
  • compile() : 将正则字符串编译成正则对象,方便在后面的匹配中复用。
    注:一般在匹配字符串的时候,总会有换行,所以可以在调用函数的时候,加入re.S参数,可忽略换行。

下面将用正则表达式为例,爬取起点网玄幻小说的排行榜:
首先打开起点网,玄幻小说栏目(https://www.qidian.com/rank/click?style=1&page=
我们将爬取排名,书本连接,书名,作者,还有时间。

image.png

打开谷歌开发者环境,找到我们要爬取的目标。
1、我们先用requests.get()方法,获取目标网页。

import requests
def get_one_page(url):
    response = requests.get(url)
    return response.text

函数返回为目标网页的源码。
2、解析源网页,每一本书都是包含在ul标签下的li标签里,里面包含了我们需要的几个内容。接下来,我们将写出正则表达式匹配到我们想要的内容。


image.png
import re
def parse_one_page(html):
    pattern = re.compile('<li\sdata-rid.*?<span class="rank-tag no.*?">(.*?)<cite>.*?<h4><a\shref="(.*?)".*?>(.*?)</a></h4>',re.S)
    items = re.findall(pattern,html)
    return items

调用该函数,查看返回结果是什么形式的。

[('1', '//book.qidian.com/info/1010191960', '大王饶命'), ('2', '//book.qidian.com/info/1209977', '斗破苍穹'), ('3', '//book.qidian.com/info/1011705052', '明朝败家子'), ('4', '//book.qidian.com/info/1012486119', '十恶临城'), ('5', '//book.qidian.com/info/1002409852', '诡神冢'), ('6', '//book.qidian.com/info/1011483714', '怪物聊天群'), ('7', '//book.qidian.com/info/1010276884', '狼牙兵王'), ('8', '//book.qidian.com/info/1012237441', '全球高武'), ('9', '//book.qidian.com/info/1011816096', '全职武神'), ('10', '//book.qidian.com/info/1009704712', '牧神记'), ('11', '//book.qidian.com/info/1012749331', '重回80当大佬'), ('12', '//book.qidian.com/info/1010981643', '开天录'), ('13', '//book.qidian.com/info/1004608738', '圣墟'), ('14', '//book.qidian.com/info/3602691', '修真聊天群'), ('15', '//book.qidian.com/info/1011449952', '我在帝都建洞天'), ('16', '//book.qidian.com/info/1010730481', '神级大药师'), ('17', '//book.qidian.com/info/3393401', '极品全能学生'), ('18', '//book.qidian.com/info/1011468740', '我要大宝箱'), ('19', '//book.qidian.com/info/118447', '星辰变'), ('20', '//book.qidian.com/info/1010734492', '凡人修仙之仙界篇')]

可以看到返回结果是列表形式的,里面的元素元组,现在我们已经成功的获取到我们需要的数据了,下面处理并输出到文本里。

3.保存到文本文件中

def write_to_txt(content):
  with open('起点小说排行榜.txt','a',encoding='utf-8') as f:
    f.write(content+'\n')

这便是爬取一个页面并保存数据的三个核心步骤,而我们需要前XX名的,翻看第二页的链接,发现只需在后面加上page = i 页即可
所以可以把我们的url 成'https://www.qidian.com/rank/click?style=1&page='+str(page)即可。

下面附上完整的代码:

import requests
import re
import json
url = ''
def get_one_page(url):
    response = requests.get(url)
    
    return response.text

def parse_one_page(html):
    pattern = re.compile('<li\sdata-rid.*?<span class="rank-tag no.*?">(.*?)<cite>.*?<h4><a\shref="(.*?)".*?>(.*?)</a></h4>',re.S)
    items = re.findall(pattern,html)
    return items

def main(page):
    baseurl = 'https://www.qidian.com/rank/click?style=1&page='
    url = baseurl + str(page)
    
    html = get_one_page(url)
    for item in parse_one_page(html):
        with open('起点小说排行榜.txt','a',encoding='utf-8') as f:
            num = int(item[0]) + page*20 
            f.write(str(num)+"   "+"https:"+item[1]+"   "+item[2]+'\n')
    print('第'+str(page)+'页爬取成功')

if __name__ =='__main__':
    for i in range(26):
        main(i)

至此,我们便可以爬到起点网玄幻小说排名了。

Beautiful Soup

相比于正则,Beautiful Soup 选取标签的方法,会使解析页面更加的灵活。共有四种解析方法,我一般用BeautifulSoup(markup,'lxml')来解析。
基础用法:

from bs4 import BeautifulSoup 
soup = BeautifulSoup('<p>Hello</p>','lxml')
print(soup.p.string)

结果将输出:

Hello 

Beautiful Soup有三种选择器:

  • 节点选择器 : 如果返回的是单个节点,可以直接string,attrs输出文本和属性,如果是多个节点的生成器,可以转化为列表后取出某个元素,再调用string, attrs输出文本和属性。
  • 方法选择器 : find_all()和fing()。
  • CSS选择器 : 获取文本可以用string ,也可以用get_text()方法。

下面将用Beautiful Soup 方法解析起点网玄幻小说排行榜:
抓取页面与正则相同,主要是解析不同,Beautiful Soup的解析代码为:

def parse_one_page(html):
    soup = BeautifulSoup(html,'lxml')
    contents = []
    for item in soup.select('.rank-view-list li'):
        num = item.select('span')[0].get_text()
        src ='https:' + item.select('a')[0].attrs['href']
        book_name = item.select('h4 a')[0].get_text()
        author = item.select('p a')[0].get_text()
        date = item.select('.update span')[0].get_text()
        content = num + "   " + src + "   " + book_name + "   " + author + "   " + date
        contents.append(content)    
    return contents

将需要的内容存放再一个列表里,打印列表,结果为:

['1   https://book.qidian.com/info/1010191960   大王饶命   会说话的肘子   2018-10-26 20:54', '2   https://book.qidian.com/info/1209977   斗破苍穹   天蚕土豆
 2018-09-19 09:59', '3   https://book.qidian.com/info/1011705052   明朝败家子   上山打老虎额   2018-10-26 19:00', '4   https://book.qidian.com/info/1012486119   十恶临城   言桄   2018-10-26 18:31', '5   https://book.qidian.com/info/1002409852   诡神冢   焚天孔雀   2018-10-26 18:31', '6   https://book.qidian.com/info/1011483714   怪物聊天群   泛舟填词   2018-10-26 19:02', '7   https://book.qidian.com/info/1010276884   狼牙兵王   蝼蚁望天   2018-10-26 21:41', '8   https://book.qidian.com/info/1012237441   全球高武   老鹰吃小鸡   2018-10-26 20:27', '9   https://book.qidian.com/info/1011816096   全职武神   流浪的蛤蟆   2018-10-26 10:00', '10   https://book.qidian.com/info/1009704712   牧神记   宅猪   2018-10-26 20:05', '11   https://book.qidian.com/info/1012749331   重回80当大佬   浙东匹夫   2018-10-26 07:49', '12   https://book.qidian.com/info/1010981643   开天录   血红   2018-10-26 12:00', '13   https://book.qidian.com/info/1004608738
 圣墟   辰东   2018-10-25 23:43', '14   https://book.qidian.com/info/3602691   修真聊天群   圣骑士的传说   2018-10-26 00:00', '15   https://book.qidian.com/info/1011449952   我在帝都建洞天   万事皆虚   2018-10-26 18:11', '16   https://book.qidian.com/info/1010730481   神级大药师   微了个信   2018-10-26 21:32', '17
  https://book.qidian.com/info/3393401   极品全能学生   花都大少   2018-10-26 17:00', '18   https://book.qidian.com/info/1011468740   我要大宝箱   风云指上
2018-10-26 20:00', '19   https://book.qidian.com/info/118447   星辰变   我吃西红柿   2017-09-21 10:23', '20   https://book.qidian.com/info/1010734492   凡人修仙之仙界篇   忘语   2018-10-26 12:30']

返回结果是列表形式的。
后面同样的保存到本地,再爬取其他页,完整的代码如下:

import requests
import pandas
from bs4 import BeautifulSoup

url = 'https://www.qidian.com/rank/click?style=1&page='

def get_one_page(url):
    response = requests.get(url)
    
    return response.text

def parse_one_page(html):
    soup = BeautifulSoup(html,'lxml')
    contents = []
    for item in soup.select('.rank-view-list li'):
        num = item.select('span')[0].get_text()
        src ='https:' + item.select('a')[0].attrs['href']
        book_name = item.select('h4 a')[0].get_text()
        author = item.select('p a')[0].get_text()
        date = item.select('.update span')[0].get_text()
        content = num + "   " + src + "   " + book_name + "   " + author + "   " + date
        contents.append(content)
    
    return contents
    

def write_to_text(content):
    with open('qidian.txt','a',encoding='utf-8') as w:
        w.write(content + '\n')
       

def main(page):
    url = 'https://www.qidian.com/rank/click?style=1&page=' + str(page)
    html = get_one_page(url)    
    for content in parse_one_page(html):
        write_to_text(content)

if __name__ == '__main__': 
    for page in range(10):
        main(page)

总结

正则解析页面,通用性强,Beautiful Soup则更加简单,更容易理解。

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