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

2018-10-26  本文已影响0人  机智的柠檬

爬虫概述

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

一、发出请求

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

1.1 urllib的使用

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

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)
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()方法向浏览器发起请求。

二、解析页面

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

2.1 正则表达式

常用的正则表达式规则:

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

常用的方法有:

下面将用正则表达式为例,爬取起点网玄幻小说的排行榜:
首先打开起点网,玄幻小说栏目(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有三种选择器:

下面将用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则更加简单,更容易理解。

上一篇下一篇

猜你喜欢

热点阅读