爬虫八:从0开始(一):基本请求、正则爬取、beautiful的简单使用

http://www.zhyea.com/2016/08/13/python-spider-4-multi-thread.html

一、Python网络爬虫1 – 简单的Http请求:

from urllib import urlopen
url = 'http://www.zhyea.com/2016/07/17/memory-analyzer-all.html'
response = urlopen(url)
content = response.read()
print type(content)
# out:   <type 'str'>

通常,在命令行打印出来的是网页的源代码。想从中过滤出来需要的信息需要进行匹配和筛选。比如使用正则式匹配获取title和body中的内容:

import re
title = re.search(r"<title>.*</title>", content)
print "title>>>>>>>>>>>",title
# out:    title>>>>>>>>>>> <title>MemoryAnalyzer介绍及使用 | ZY笔记</title>
body = re.search(r"<body[\w|\W]*</body>", content)
print "body>>>>>>>>>>>",body

归纳:将get, post, get_something写成一般的、通用的函数

import re
from urllib import urlopen
from urllib import urlencode
def get(url):
    response = urlopen(url)
    content = ""
    if response:
        content = response.read().decode("utf8")
        response.close()
    return content
def post(url, **paras):
    import requests
    # param = urlencode(paras).encode('utf8')
    param = urlencode(paras)
    rep = requests.post(url, data=param)
    content = ""
    if rep:
        content = rep.content.decode("utf8")
        rep.close()
    return content
def get_target(pattern, content):
    m = re.search(pattern, content)
    target = ""
    if m:
        target = m.group(0)
    return target
def main(url):
    content = get(url)
    title = get_target(r"<title>.*<\title>", content)
    body = get_target(r"<body[\w|\W]*<body>", content)
  print "title>>>>>>>>>>>", title
  print "body>>>>>>>>>>>", body

 

二、Python网络爬虫2 – 请求中遇到的几个问题:

继续使用以上方法爬虫,出现错误:

 

Traceback (most recent call last):
  File "D:/PythonDevelop/spider/grab.py", line 22, in <module>
    main()
  File "D:/PythonDevelop/spider/grab.py", line 17, in main
    content = get(url)
  File "D:/PythonDevelop/spider/grab.py", line 7, in get
    response = request.urlopen(url)
  File "D:\Program Files\python\python35\lib\urllib\request.py", line 162, in urlopen
    return opener.open(url, data, timeout)
  File "D:\Program Files\python\python35\lib\urllib\request.py", line 465, in open
    response = self._open(req, data)
  File "D:\Program Files\python\python35\lib\urllib\request.py", line 483, in _open
    '_open', req)
  File "D:\Program Files\python\python35\lib\urllib\request.py", line 443, in _call_chain
    result = func(*args)
  File "D:\Program Files\python\python35\lib\urllib\request.py", line 1268, in http_open
    return self.do_open(http.client.HTTPConnection, req)
  File "D:\Program Files\python\python35\lib\urllib\request.py", line 1240, in do_open
    h.request(req.get_method(), req.selector, req.data, headers)
  File "D:\Program Files\python\python35\lib\http\client.py", line 1083, in request
    self._send_request(method, url, body, headers)
  File "D:\Program Files\python\python35\lib\http\client.py", line 1118, in _send_request
    self.putrequest(method, url, **skips)
  File "D:\Program Files\python\python35\lib\http\client.py", line 960, in putrequest
    self._output(request.encode('ascii'))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-12: ordinal not in range(128)

根据错误栈信息可以看出是在发送http请求时报错的,是因为编码导致的错误。在python中使用中文经常会遇到这样的问题。因为是在http请求中出现的中文编码异常,所以可以考虑使用urlencode加密。

在python中对字符串进行urlencode编码,使用的是urllib库中的quote

from urllib import quote
print quote("蝙蝠侠")
# out:     %E8%9D%99%E8%9D%A0%E4%BE%A0

 总结:

1.首先去你要去的网站,测试编码是什么格式的是utf8或者gb2312

2.然后把要编码的文字encode成所需格式

3.最后进行quote或urlencode

示例:

from urllib import quote
test1 = '美国队长'.encode('gb2312')
test1_1 = quote(test1)
print(test1_1)

 

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe7 in position 0: ordinal not in range(128)

 

但是抛出异常,原因:'美国队长'是utf8字符串在python2中使用的是unicode,因此不能直接由utf8编码为gb2312,中间需要经过一层unicode,即先将utf8解码为unicode然后再编码为gb2312。

from urllib import quote
test1 = '美国队长'.decode('utf8').encode('gb2312')
test1_1 = quote(test1)
print(test1_1)
# OUT:    %C3%C0%B9%FA%B6%D3%B3%A4

如果不需要编码为gb2312,即网站不是gb2312编码;如果网站需要的是utf8编码,那么即使在python2中,也可以直接使用utf8,不需要多一层编码解码;直接使用urlencode编码即可。

test1 = '美国队长'
test1_1 = quote(test1)
print(test1_1)

# OUT: %E7%BE%8E%E5%9B%BD%E9%98%9F%E9%95%BF

 但是如果字符串是unicode,直接编码urlencode将抛也异常。因为在urlencode或quote编码之前,必须要将之转成utf8。总结中的第二条原则。

test1 = u'美国队长'
test1_1 = quote(test1)
print(test1_1)
# OUT:    KeyError: u'\u7f8e'

 

from urllib import quote, unquote, urlencode
test1 = u'美国队长'.encode('utf8')
test1_1 = quote(test1)
print(test1_1)+
#  OUT:    %E7%BE%8E%E5%9B%BD%E9%98%9F%E9%95%BF

%C3%C0%B9%FA%B6%D3%B3%A4是web程序中使用的一种编码方式。

相反,从web中取出来的字符串,使用unquote解码为编程语言的字符串。但是,和编码一样,要注意网站使用的编码;如果网站使用的gb2312编码,那么使用urlencode解码以后,还要使用gb2312解码为unicode,否则乱码。如果网站使用的是utf8,那么也应该解码除了urlencode解码,还要使用utf8解码为unicode (当然不从utf8解码为unicode也不会出乱码)。

print unquote("%C3%C0%B9%FA%B6%D3%B3%A4").decode('gb2312')  # 由于这串字符串是gb2312编码,所以解码后,还需从gb2312解码,否则会是乱码。

 

print unquote("%E7%BE%8E%E5%9B%BD%E9%98%9F%E9%95%BF")    # 这串web编码为utf8,直接unquote解码为utf8,正常,不会抛错。当然也可以再解码为unicode
print unquote("%E7%BE%8E%E5%9B%BD%E9%98%9F%E9%95%BF").decode('utf8')

 

或urlencode方法;

print urlencode({'spam': 1, 'eggs': 2, 'bacon': 0})
# OUT:    eggs=2&bacon=0&spam=1

 

同样,在urlencode编码之前,必须是utf8,否则将同quote编码一样,抛也异常。总结中的第二原则。因此准确的说是在urlencode和quote编码之前,一定要先将字符串或字典编码为网站需要的编码,然后才能使urlencode和quote进行web编码。

因为网站上一般都不会使用unicode编码,所以在使用web编码之前,一般都需要将python2的unicode进行编码。

print urlencode({'spam': u'美国队长', 'eggs': 2, 'bacon': 0})
#  OUT:   UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

 

print urlencode({'spam': u'\u7f8e\u56fd\u961f\u957f'.encode('utf8'), 'eggs': 2, 'bacon': 0})
# OUT:    eggs=2&bacon=0&spam=%E7%BE%8E%E5%9B%BD%E9%98%9F%E9%95%BF

或者

print json.dumps({'spam': u'\u7f8e\u56fd\u961f\u957f', 'eggs': 2, 'bacon': 0}, ensure_ascii=False)    # 使用utf8序列化

 整理:

#!python
# encoding: utf-8
from urllib import request
from urllib import parse

DEFAULT_HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"}
DEFAULT_TIMEOUT = 120


def get(url):
    req = request.Request(url, headers=DEFAULT_HEADERS)
    response = request.urlopen(req, timeout=DEFAULT_TIMEOUT)
    content = ""
    if response:
        content = response.read().decode("utf8")
        response.close()
    return content


def post(url, **paras):
    param = parse.urlencode(paras).encode("utf8")
    req = request.Request(url, param, headers=DEFAULT_HEADERS)
    response = request.urlopen(req, timeout=DEFAULT_TIMEOUT)
    content = ""
    if response:
        content = response.read().decode("utf8")
        response.close()
    return content


def main():
    url = "https://www.torrentkitty.tv/search/"
    get_content = post(url, q=parse.quote("蝙蝠侠"))
    print(get_content)
    get_content = get(url)
    print(get_content)


if __name__ == "__main__":
    main()

 

三、Python网络爬虫3 – 使用BeautifulSoup解析网页:

在第一节演示过如何使用正则表达式截取网页内容。不过html是比正则表达式更高一级的语言,仅仅使用正则表达式来获取内容还是有些困难的。

这次会使用一个新的工具:python的BeautifulSoup库,BeautifulSoup是用来从HTML或XML文件中提取数据的工具。

BeautifulSoup需要先安装才能使用。关于BeautifulSoup安装和使用可以参考这份文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/

最好使用chrome浏览器,因为chrome浏览器的Developer Tools(类似FireBug)有一个功能可以获取CSS选择器(选中目标 –> Copy –> Copy selector),这是很有用的(有的时候也会出现问题,不过还是可以用来做参考的)

1.先使用BeautifulSoup获取title试试:

html_doc = get("https://www.douban.com/")
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, "html.parser")
print soup.select("title")[0]
# OUT:  <title>豆瓣</title>

这里是用了CSS选择器来获取HTML中的内容。

在搜索结果中,点击每个结果项右侧的“open”按钮可以打开下载。使用DeveloperTools可以看到“open”按钮实际上是一个超链接,超链接指向的是一个磁力链接。这个磁力链接就是我们要采集的目标。使用Chrome的DeveloperTools的选择器选中任意一个“open”按钮,然后在Elements项中,找到我们选中的项的源码(很容易找到,选中项的背景是蓝色的),右键 –> Copy –> Copy selector可以获取到这个按钮的CSS选择器:

#archiveResult > tbody > tr:nth-child(10) > td.action > a:nth-child(2)

 

将这个选择器放到代码中却是不生效的:

def detect(html_doc):
    soup = BeautifulSoup(html_doc, "html.parser")
    print(len(soup.select("#archiveResult > tbody > tr:nth-child(10) > td.action > a:nth-child(2)")))

执行结果输出的是0。soup.select()方法返回的是一个列表,长度为0……好像不用解释了。

python是不支持上面的选择器的部分语法的,比如nth或者tbody,修改下就可以了:

去掉了tbody;将nth-of-type(10)-->改为nth-of-type(10)

print(soup.select("#archiveResult > tr:nth-of-type(10) > td.action > a:nth-of-type(2)"))

 

直接执行上面的代码就可以得到一个超链接的源码:

[<a title="[BT乐园·bt606.com]蝙蝠侠大战超人:正义黎明.BD1080P.X264.AAC.中英字幕"
href="magnet:?xt=urn:btih:DD6A680A7AE85F290A76826AA4D2E72194975EC8&dn=%5BBT%E4%B9%90%E5%9B%AD%C2%B7bt606.com%5D%E8%9D%99%E8%9D%A0%E4%BE%A0%E5%A4%A7%E6%88%98%E8%B6%85%E4%BA%BA%EF%BC%9A%E6%AD%A3%E4%B9%89%E9%BB%8E%E6%98%8E.BD1080P.X264.AAC.%E4%B8%AD%E8%8B%B1%E5%AD%97%E5%B9%95&tr=http%3A%2F%2Ftracker.ktxp.com%3A6868%2Fannounce&tr=http%3A%2F%2Ftracker.ktxp.com%3A7070%2Fannounce&tr=udp%3A%2F%2Ftracker.ktxp.com%3A6868%2Fannounce&tr=udp%3A%2F%2Ftracker.ktxp.com%3A7070%2Fannounce&tr=http%3A%2F%2Fbtfans.3322.org%3A8000%2Fannounce&tr=http%3A%2F%2Fbtfans.3322.org%3A8080%2Fannounce&tr=http%3A%2F%2Fbtfans.3322.org%3A6969%2Fannounce&tr=http%3A%2F%2Ftracker.bittorrent.am%2Fannounce&tr=udp%3A%2F%2Ftracker.bitcomet.net%3A8080%2Fannounce&tr=http%3A%2F%2Ftk3.5qzone.net%3A8080%2F&tr=http%3A%2F%2Ftracker.btzero.net%3A8080%2Fannounce&tr=http%3A%2F%2Fscubt.wjl.cn%3A8080%2Fannounce&tr=http%3A%2F%2Fbt.popgo.net%3A7456%2Fannounce&tr=http%3A%2F%2Fthetracker.org%2Fannounce&tr=http%3A%2F%2Ftracker.prq.to%2Fannounce&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce&tr=http%3A%2F%2Ftracker.dmhy.org%3A8000%2Fannounce&tr=http%3A%2F%2Fbt.titapark.com%3A2710%2Fannounce&tr=http%3A%2F%2Ftracker.tjgame.enorth.com.cn%3A8000%2Fannounce&"
rel="magnet">Open</a>]

 

超链接中的href和title属性就是我们的目标。BeautifulSoup也提供了获取属性的方案,select方法返回的每个值中都包含一个attrs字典,可以从字典中获取到相关的属性信息:

def detect(html_doc):
    html_soup = BeautifulSoup(html_doc, "html.parser")
    anchor = html_soup.select("#archiveResult > tr:nth-of-type(10) > td.action > a:nth-of-type(2)")[0]
    print(anchor.attrs['href'])
    print(anchor.attrs['title'])

好了,大体就是这样。

不过程序中最难看的就是获取超链接的方案:一个一个地获取是不可能。好在BeautifulSoup支持通过属性的值来获取对象,最后调整下就是这样子了:

def detect(html_doc):
    html_soup = BeautifulSoup(html_doc, "html.parser")
    anchors = html_soup.select('a[href^="magnet:?xt"]')
    for i in range(len(anchors)):
        print(anchors[i].attrs['title'])
        print(anchors[i].attrs['href'])

上面的代码中的a[href^=”magnet:?xt”]表示查询的是所有<a>标签,且<a>标签的href属性需要以“magnet:?xt”开头。(看到“^”有没有觉得熟悉,这个“^”和正则式中的“^”意义是一样的)。通过这个select方法得到<a>标签列表,然后遍历标签列表,从标签的attrs字典中读取到相关的属性信息。

#!python
# encoding: utf-8
from urllib import request
from urllib import parse
from bs4 import BeautifulSoup

DEFAULT_HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"}
DEFAULT_TIMEOUT = 360


def get(url):
    req = request.Request(url, headers=DEFAULT_HEADERS)
    response = request.urlopen(req, timeout=DEFAULT_TIMEOUT)
    content = ""
    if response:
        content = response.read().decode("utf8")
        response.close()
    return content


def post(url, **paras):
    param = parse.urlencode(paras).encode("utf8")
    req = request.Request(url, param, headers=DEFAULT_HEADERS)
    response = request.urlopen(req, timeout=DEFAULT_TIMEOUT)
    content = ""
    if response:
        content = response.read().decode("utf8")
        response.close()
    return content


def detect(html_doc):
    html_soup = BeautifulSoup(html_doc, "html.parser")
    anchors = html_soup.select('a[href^="magnet:?xt"]')
    for i in range(len(anchors)):
        print(anchors[i].attrs['title'])
        print(anchors[i].attrs['href'])


def main():
    url = "https://www.torrentkitty.tv/search/"
    html_doc = post(url, q=parse.quote("超人"))
    detect(html_doc)


if __name__ == "__main__":
    main()

 

posted on 2018-03-02 16:44  myworldworld  阅读(431)  评论(0)    收藏  举报

导航