Python_Crawling学习记录

为了调用API跑来学爬虫。


#

本文是笔者学习尚硅谷Python教程时做的学习笔记。学习了以下有关 Python 的库和相关插件的使用方法:

urllib与Requests

本文中提到了很多库,但在实际的使用中,并不是每一个库都会被使用到。如 urllib 库是学习过程中使用到的第一个有关 URLs 访问的库,但由于存在请求头定制和请求参数编码等问题,使用该库较为麻烦,所以在以后的开发中,笔者使用 Requests 库来访问 URLs 会多一些,因为后者较为简便。

XPath与JSONPath

学习完 urllib 之后,便拥有了访问特定 URLs 的能力,接下来学习的 XPath  JSONPath 实际上用来解析数据,区别在于——前者是解析 HTML 数据的XML路径语言,后者是解析 JSON 数据的库。

 XPath 只是语言,在 Python 里通过其他库里的方法来使用该XML路径语言,比如 lxml.etree 里的 xpath() 方法,或后面学到的 Selenium 里面的 find_element() 方法等等。

在实际开发中,会访问服务器接口并得到返回的 JSON 格式的数据,学习 JSONPath 来解析数据得到有用信息。

lxml与Beautiful Soup

 lxml  Beautiful Soup 都是处理 XML HTML 数据的库,前者可以结合 XPath 使用,后者通过 find() 等方法及属性、CSS选择器等进行节点选择。

Selenium

 Selenium 是装载浏览器驱动进行网页模拟访问的Web自动化测试工具,测试运行过程中就像真正的用户在操作。有一些网页,如京东的 Flash Sale ,需要真正使用浏览器访问才能加载,使用 urllib  Requests 进行模拟访问是不会返回相关结果的。

由于操作真正的浏览器进行访问,浏览器会进行页面渲染,耗费大量资源。因此在 Selenium 章节中介绍了 Chrome Headless,使用这种方法以达到驱动真实浏览器进行模拟访问而不渲染页面并节省资源的目的。

Scrapy

大项目使用到的框架,本文并未详细介绍。

# 序列化和反序列化

在文件写入的时候,只能将字符串写入到文件中,像字典、列表那些都不能直接写入,必须先执行序列化才能写入文件。

序列化:把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

使用 json 模块的 dumps()  dump() 函数都可以进行序列化。不同的是,后者能够在参数里填写打开的文件以减少步骤。

# 使用dumps()方法
import json

file_1 = open("test.txt", mode="w")
tiny_list = ["a", "b"]
file_1.write(json.dumps(tiny_list))
file_1.close()


# 使用dump()方法
import json

file_1 = open("test.txt", mode="w")
tiny_list = ["b", "a"]
json.dump(tiny_list, file_1)
file_1.close()

反序列化:

 json.load()  json.loads() 的区别是,后者可以直接打开内容且自动编码,而前者需要使用 open() 方法打开文件(json 文件同理)。

# 使用loads()方法
import json

file_1 = open("test.txt", mode="r")
file_content = json.loads(file_1.read())
file_1.close()
print(type(file_content))


# 使用load()方法
import json

file_1 = open("test.txt", mode="r")
file_content = json.load(file_1)
file_1.close()
print(type(file_content))

如果需要打开 json 文件,也使用以上方法。

json_file = open('./poi_around.json', encoding='UTF-8', mode='r')
json_object = json.load(json_file)
json_file.close()

# urllib

Python 自带的库,能够模拟浏览器向服务器发送请求。

import urllib.request

# 定义一个URL
url = "https://baidu.com"

# 使用urllib模拟浏览器打开url,并接收返回的response
response = urllib.request.urlopen(url)

# 读取response的内容,read()返回的是字节形式的二进制数据
# 需要将二进制数据转换为字符串,这个过程叫解码 decode
# 使用meta标签里的charset来decode
content = response.read().decode("utf-8")

print(content)

上述代码中,content 的数据类型是 HTTPResponse。下面介绍一些方法:

# 读取前6个字节
response.read(6)

# 读取一行
response.readline()

# 按行读取,直到读完
response.readlines()

# 获取状态码
response.getcode()

# 获取url
response.geturl()

# 获取响应头(状态信息)
response.getheaders()

通过urllib下载文件:

import urllib.request

# 定义一个文件链接
url_download = "xxx.jpg"
# 使用urlretrieve()下载文件
urllib.request.urlretrieve(url_download, "想要的名字.格式")

URL定制:

协议+主机+端口号+路径+参数+锚点

参数是 ? 之后用&连接起来的那一串。锚点是 # 之后的数据。

由于网站的反爬,我们需要打开自己的浏览器通过 Inspect -> Network,选择主机找到自己真实的UA。并将UA放到一个定义的字典里。但 urlopen() 不能传递字典参数,但可以传递 request对象,所以要进行URL定制。

url = "https://baidu.com/"
headers = {"User-Agent": "Mozilla/5.0"}
request = urllib.request.Request(url=url, headers=headers)
response = urllib.request.urlopen(request)

编解码:

之前在 URL 里直接写中文的方式是错误的,需要使用 urllib.parse.quote() 方法将汉字转换为对应的Unicode。

import urllib.parse

url = "https://baidu.com/s?wd=" + urllib.parse.quote("中文测试")

但如果 URL 中参数有多个的时候,使用 urllib.parse.quote() 就会比较麻烦。所以下面提出 urllib.parse.urlencode() 方法的使用。这个方法不仅可以将汉字转换为 unicode 编码,还能将字典中的键值对用 & 连接起来。

import urllib.parse

base_url = "www.baidu.com/s?"
para = {
    "wd": "测试",
    "gender": "无性别",
    "cc": "参数"
}
url = base_url + urllib.parse.urlencode(para)

POST请求:

POST 请求参数不会拼接在 URL 后面,需要使用 URL定制。且 POST 请求必须编码成字节型数据,需要使用 urlencode().encode(“utf-8”) 。接下来调用百度翻译作为演示。

import urllib.request
import urllib.parse
import json

headers = {"User-Agent": "Mozilla/5.0"}
base_url = "https://fanyi.baidu.com/sug"
# post的参数在Form Data里找
post_data = {"kw": "crawl"}
# 对POST请求进行urlencode()之后还要encode("utf-8")
request = urllib.request.Request(url=base_url, headers=headers, data=urllib.parse.urlencode(post_data).encode("utf-8"))
response = urllib.request.urlopen(request)
content = response.read().decode("utf-8")
# 需要将返回的json数据转换成json对象才能显示结果
print(json.loads(content))

请求百度翻译的详细翻译,涉及到 Cookie 反爬以及多个 POST 请求参数。

import urllib.request
import urllib.parse
import json

headers = {"Cookie": "BIDUPSID="}
base_url = "https://fanyi.baidu.com/v2transapi?from=en&to=zh"
post_data = {
    "from": "en",
    "to": "zh",
    "query": "detail",
    "simple_means_flag": "3",
    "sign": "756271.1009950",
    "token": "099f4a898364b930ffabbde1b1e2af1e",
    "domain": "common"
}
request = urllib.request.Request(url=base_url, headers=headers, data=urllib.parse.urlencode(post_data).encode("utf-8"))
response = urllib.request.urlopen(request)
content = response.read().decode("utf-8")
print(json.loads(content))

AJAX的GET请求:

首先写一个获取豆瓣第一页推荐电影并保存到本地的代码。

import urllib.request
import urllib.parse

base_url = "https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action=&start=0&limit=20"
headers = {"User-Agent": "Mozilla/5.0"}
request = urllib.request.Request(url=base_url, headers=headers)
response = urllib.request.urlopen(request)
content = response.read().decode("utf-8")
# 下载数据到本地,因为数据有中文,所以保存的时候应该加一个encoding参数
doban_file = open("douban.json", mode="w", encoding="utf-8")
doban_file.write(content)
doban_file.close()

因为豆瓣排行榜页面用了动态加载技术,所以需要输入指令让计算机翻到下一页,然后爬取下一页的内容。在豆瓣这个案例中,需要清空一下 NETWORK,往下翻找到第二页的接口,对比一下一二页接口的规律,然后写出前十页的接口依次访问获取数据。

import urllib.request
import urllib.parse

base_url = "https://movie.douban.com/j/chart/top_list?type=24&interval_id=100%3A90&action=&"
start_page = input("请输入起始页数:")
end_page = input("请输入终止页数")
headers = {"User-Agent": "Mozilla/5.0"}


def init_request(num: int):
    para = {
        "start": (num - 1) * 20,
        "limit": 20
    }
    url = base_url + urllib.parse.urlencode(para)
    inited_request = urllib.request.Request(url=url, headers=headers)
    return inited_request


def process_crawling(a_request):
    a_response = urllib.request.urlopen(a_request)
    return a_response


def download_json(a_response, num):
    content = a_response.read().decode("utf-8")
    a_json = open("douban_" + str(num) + ".json", "w", encoding="utf-8")
    a_json.write(content)
    a_json.close()


for page_num in range(int(start_page), int(end_page) + 1):
    request = init_request(page_num)
    response = process_crawling(request)
    download_json(response, page_num)
print("工作完成")

重点是从浏览器 NETWORK 里面找数据包里的地址,而不是看浏览器地址。

AJAX的POST请求:

当在头部中看到 X-Requested-WidthXMLHttpRequest 时应当判断为AJAX响应。同时还要注意 Form Data 里的键值对。

其他操作和上述POST请求一致。

异常:

在执行过程中可能会遇到URLError/HTTPError,后者是前者的子类。

Cookie登陆:

有些数据需要登陆平台之后才能采集,这里就需要使用Cookie登陆。登陆后在 Request Headers 中找到 Cookie & Referer ,前者是登陆信息,后者突破防盗链需要用到。在爬取数据过程中一般来说只需要这两个要素,但有些网站需要用到动态Cookie,在下面 Handler 小节中讲解。

Handler处理器:

Handler处理器可以定制更高级的请求头,包括动态Cookie和代理等。使用Handler处理器有三步,需要按步骤执行。

import urllib.request

base_url = "https://www.baidu.com/"
headers = {
    'User-Agent': 'Mozilla/5.0'
}
request = urllib.request.Request(url=base_url, headers=headers)
# 创建Handler对象
handler = urllib.request.HTTPHandler()
# 创建opener对象
opener = urllib.request.build_opener(handler)
# 调用open方法
response = opener.open(request)
content = response.read().decode("utf-8")
print(content)

设置代理:

import urllib.request

base_url = "https://www.baidu.com/"
headers = {
    'User-Agent': 'Mozilla/5.0'
}
request = urllib.request.Request(url=base_url, headers=headers)
# 创建proxy字典
proxy = {
    "http": "127.0.0.1:2233"
}
# 创建ProxyHandler对象
proxy_handler = urllib.request.ProxyHandler(proxies=proxy)
# 创建opener对象
opener = urllib.request.build_opener(proxy_handler)
# 调用open方法
response = opener.open(request)

代理可以使用快代理或其他服务商获得。但如果高强度获取某个服务,则需要使用代理池。所谓代理池是一个列表,放着代理字典。

proxy_pool = [
    {"http": "127.0.0.1:2233"}
    {"http": "127.0.0.2:2234"}
]

import random
proxy = random.choice(proxy_pool)

# XPath

获取网页源码后需要使用 XPath 插件来解析源码以得到想要的数据。该插件是浏览器插件,不是 Python 的库。

使用方法:Shift+Ctrl+X

在浏览器中安装好 XPath 后,还需要给 Python 安装 lxml 库,需要进入 CMD 使用 pip 安装到 Python 解释器目录下的 Scripts 目录里。

cd Python/Scripts/
pip install lxml

解析本地文件和网页响应数据会使用到不同的方法。

from lxml import etree

# 解析本地文件        etree.parse()
# 解析服务器响应      etree.HTML()

local_file_tree = etree.parse("local_html.html")
server_response_tree = etree.HTML(response.read().decode("utf-8"))

下面是关于 lxml 的语法。

local_file_tree = etree.parse("local_html.html")

# 路径查询:使用//查找所有子孙节点,不考虑层级关系,使用/查找子节点。
tree_list = local_file_tree.xpath("//body/ul/li")
# 谓词查询:查找带有id,带有某个id的节点,或id的值
tree_list = local_file_tree.xpath("//body/ul/li[@id]")
tree_list = local_file_tree.xpath("//body/ul/li[@id='label1']")
tree_list = local_file_tree.xpath("//ul/li[@class='class2']/@id")
# 属性查询:查找带有class的节点,带有某个class的节点,或class的值
tree_list = local_file_tree.xpath("//ul/li[@class]")
tree_list = local_file_tree.xpath("//ul/li[@class='class1']")
tree_list = local_file_tree.xpath("//ul/li[@class='class2']/@id")
# 模糊查询
tree_list = local_file_tree.xpath("//ul/li[contains(@class, 'cla')]")
tree_list = local_file_tree.xpath("//body/ul/li[starts-with(@id, 'l')]")
# 逻辑查询:与用and,或用|
tree_list = local_file_tree.xpath("//body/ul/li[@id='label1' and @class='class2']")
tree_list = local_file_tree.xpath("//body/ul/li[@id='l1'] | //body/ul/li[@class='c1']")
# 显示查询到的内容
tree_list = local_file_tree.xpath("//body/ul/li/text()")

 lxml  XPath 结合起来使用:

from lxml import etree
import urllib.request
import urllib.parse

base_url = "https://baidu.com/"
my_headers = {
    "User-Agent": "Mozilla/5.0"
}
my_request = urllib.request.Request(url=base_url, headers=my_headers)
response = urllib.request.urlopen(my_request)
# 需要读取解码后的响应数据,直接输入response是不行的。
server_tree = etree.HTML(response.read().decode("utf-8"))
# xpath()返回的是列表,所有跟了个[0]取第一个值,结合xpath浏览器插件来写
tree_list = server_tree.xpath("//span/input[@id='su']/@value")[0]
print(tree_list)

需要注意的是,如果遇到lazy load,则需要抓取之前的src。

# JSONPath

 JSONPath 只能解析本地数据,并且语法也与 XPath 不同。且 jsonpath.jsonpath() 返回的是一个列表,即使只找到一个结果,也会以列表的形式返回。

安装JSONPath(CMD)

pip install jsonpath

XPathJSONPathDescription
/$表示根元素
.@ 当前元素
/. or []子元素
..n/a父元素
//..递归下降,JSONPath是从E4X借鉴的。
**通配符,表示所有的元素
@n/a 属性访问字符
[][]子元素操作符
|[,]连接操作符在XPath 结果合并其它结点集合。JSONP允许name或者数组索引。
n/a[start:end:step]数组分割操作从ES4借鉴。
[]?()应用过滤表示式
n/a()脚本表达式,使用在脚本引擎下面。
()n/aXpath分组

下面是一个 JSON 文件,示例基于此 JSON

# 使用split()方法将json主体部分提取出来
content = response.read().decode("utf-8")
content = content.split("(")[1].split(")")[0]
{ "store": {
    "book": [
      { "category": "文学作品",
        "author": "钱钟书",
        "title": "围城",
        "price": 80.5
      },
      { "category": "历史作品",
        "author": "孔子",
        "title": "春秋",
        "price": 90.9
      },
      { "category": "天文作品",
        "author": "天文",
        "title": "史上最强仙人",
        "isbn": "0-553-21311-3",
        "price": 80.9
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "author": "mario",
      "color": "red",
      "price": 19.95
    }
  }
}

以下是一些示例:

import jsonpath
import json

json_object = json.load(open("books.json", encoding="utf-8"))

# 所有书的作者
book_author_list = jsonpath.jsonpath(json_object, "$.store.book[*].author")
# 所有作者
author_list = jsonpath.jsonpath(json_object, "$..author")
# store下面的所有元素
all_ele_list = jsonpath.jsonpath(json_object, "$.store.*")
# store里的所有price
store_price_list = jsonpath.jsonpath(json_object, "$.store..price")
# 第一本书
the_1st_book_list = jsonpath.jsonpath(json_object, "$..book[0]")
# 最后一本书
the_last_book_list = jsonpath.jsonpath(json_object, "$..book[(@.length-1)]")
# 前两本书  book[:2]也行,就是切片
the_1st_2nd_books_list = jsonpath.jsonpath(json_object, "$..book[0,1]")
# 过滤出所有包含isbn的书,注意,这里isbn不能是空字符串,否则会返回False。
the_isbn_books_list = jsonpath.jsonpath(json_object, "$..book[?(@.isbn)]")
# 得到所有包含address(允许空字符串)的位置
the_location_list = jsonpath.jsonpath(json_object, "$.pois[?(@.address.length >= 0)].location")
# 过滤出超过90元钱的书
the_more_than_90_books_list = jsonpath.jsonpath(json_object, "$..book[?(@.price > 90)]")

当条件较为复杂时,可以考虑使用 * 。如获得某一个公交乘车方案中步行的总距离, steps 下有一个列表,其元素也是列表。笔者尝试使用 steps[?(@..)] 获得步行的步骤,但是失败了。但使用 steps.*[?(@.)] 证明是可行的。

walk_distance = jsonpath.jsonpath(json_object, '$.result.routes[0].steps.*[?(@.vehicle_info.type == 5)].distance')

# Beautiful Soup

 Beautiful Soup  lxml 一样,是个 HTML 的解析器,用于解析和提取 HTML 中的数据。

1.安装(CMD)
pip install bs4
2.导入(py)
from bs4 import BeautifulSoup
3.创建对象,后面必须写"lxml",因为用的是lxml内核
    将服务器响应的文件用于生成一个对象
    soup = BeautifulSoup(response.read().decode(), "lxml")
    将本地的HTML文件生成一个对象
    soup = BeautifulSoup(open("local.html", encoding="utf-8"), "lxml")

Beautiful Soup使用方法:

以下假定已经通过 Beautiful Soup 打开了一个HTML文本。

1.节点定位:

# 1.根据标签名查找元素,下面这种方法只能过去第一个a元素
soup.a
# 获取第一个a元素的类型名
soup.a.name
# 获取第一个a元素的属性
soup.a.attrs

2.函数:

# 1.find()方法,只返回一个对象
soup.find('a')
soup.find('a', title='a_title')
soup.find('a', class_='a_class')

# 2.find_all()方法,返回一个符合条件的所有元素的列表
soup.find_all('a')[0]
# 查找所有a和span元素,返回一个列表
soup.find_all(['a', 'span'])[0]
# 查找前两个a元素,返回一个列表
soup.find_all('a', limit=2)

# 3.select()方法,需要用到CSS的语法,返回的是列表
soup.select('a')[0]
# 使用CSS中的表达方法,查找一个包含某个class的元素,返回一个列表
soup.select('.a_class')[0]
# 使用CSS中的表达方法,查找包含某个ID的元素,返回一个列表
soup.select('#id1')[0]

2.属性选择器和层级选择器:

# 1.属性选择器
# 查找所有包含id属性的li元素,返回一个列表
soup.select('li[id]')
# 查找所有包含id=id2属性的元素,返回一个列表
soup.select('li[id="id2"]')

# 2.层级选择器
# 后代选择器,中间是空格
soup.select('div ul')
# 子代选择器,中间是>
soup.select('div > ul > li')
# 找到所有的a和span
soup.select('a,span')

3.节点信息

# 1.获取节点的内容
obj = soup.select('#id1')[0]
print(obj.get_text())

# 2.节点的属性
obj = soul.select('#id1')[0]
# attrs输出的是一个字典
print(obj.attrs)
# 使用字典.get()可以获得键值对的值
obj.attrs.get('键')

# 3.获取节点的属性
obj = soup.select('#id1')[0]
# 使用get方法来获取字典的值
print(obj.attrs.get('class'))

案例:

获取星巴克菜单。

import urllib.request
import urllib.parse
from bs4 import BeautifulSoup
import pandas

url = 'https://www.starbucks.com.cn/menu/'
response = urllib.request.urlopen(url)
content = response.read().decode('utf-8')
soup = BeautifulSoup(content, 'lxml')
soup_list = soup.select("div[class='wrapper fluid margin page-menu-list'] li strong")
name_list = []
for a_soup in soup_list:
    name_list.append(a_soup.get_text())
df = pandas.DataFrame({
    'name': name_list
})
df.to_excel('starbucks.xlsx', index=False)

因为没有在虚拟环境中安装 lxml 包,所以使用 BeautifulSoup(content, ‘lxml’) 时报错:

Do you need to install a parser library?

因此可以将代码里的 ‘lxml’ 替换成 ‘html.parser’

# Selenium

Selenium( [化学] 硒 ),能够更真实地访问服务器。实际上,它驱动真实浏览器去访问服务器。因为真实,所以真实。

模拟浏览器进行访问时,服务器可能不会返回我们想要的数据,因此需要使用 Selenium 驱动真实浏览器内核进行访问,服务器才会给出正常情况下的所有数据。点击下面的网址下载 Chrome 的驱动,选择与计算机上 Chrome 对应的驱动版本,解压后得到 chromedriver.exe

Chrome驱动下载地址

https://chromedriver.storage.googleapis.com/index.html

安装Selenium:

CMD

pip install selenium

使用Selenium:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service


chrome_driver_path = 'chromedriver.exe'
# 生成Service对象
chrome_service = Service(executable_path=chrome_driver_path)
# 生成Chrome对象
chrome_browser = webdriver.Chrome(service=chrome_service)
# 驱动浏览器进行访问
chrome_browser.get('https://baidu.com')
chrome_browser.close()

元素定位:

使用 Selenium 驱动浏览器访问服务器时,还需要模拟鼠标点击和键盘输入。为在正确的地方执行正确的操作,需要使用 Selenium 提供的一系列元素定位方法。

from selenium.webdriver.common.by import By

# 如果要获取一个要素集,则使用find_elements()
# 1.元素定位
# 根据ID获取对象(集)
button = chrome_browser.find_element(by=By.ID, value='su')
# 根据属性的属性值获取对象(集)
input_blank = chrome_browser.find_element(by=By.NAME, value='wd')
# 根据XPath来获取对象(集)
head_wrapper = chrome_browser.find_element(by=By.XPATH, value='//div[@id="head_wrapper"]')
# 根据标签名来获取对象(集)
divs = chrome_browser.find_elements(by=By.TAG_NAME, value='div')
# 根据CSS来获取对象(集)
button = chrome_browser.find_element(by=By.CSS_SELECTOR, value='#su')
# 根据链接文本对象(集)
map_link = chrome_browser.find_element(by=By.LINK_TEXT, value='地图')

使用 find_element() 拿到的只是网页的元素,若想查看内容,则需要使用 find_element().text 

新版本需要使用 By.XX ,来选择以何种方式进行查找,所以需要 import 一下 By

元素信息及交互:

通过 find_element() 方法获取到元素对象之后,想要获取有关该元素的其他信息。

# 2.元素信息获取
# 获取元素属性
input_blank.get_attribute('class')
# 获取元素标签名
input_blank.tag_name
# 获取元素文本(两对<>之间的内容)
input_blank.text

通过 find_element() 方法获取到元素对象之后,想要进行一些其他操作,比如输入信息,点击按钮,控制浏览器向上向下滑,控制浏览器前进后退等。

# 向百度一下输入框中输入关键字
input_blank = chrome_browser.find_element(by=By.ID, value='kw')
# 清空
input_blank.clear()
input_blank.send_keys('selenium')
# 点击百度一下按钮
baiduyixia_button = chrome_browser.find_element(by=By.ID, value='su')
baiduyixia_button.click()
# 将页面划到最底部
js = 'document.documentElement.scrollTop=100000'
chrome_browser.execute_script(js)
# 点击下一页
next_page = chrome_browser.find_element(by=By.XPATH, value="//a[@class='n']")
next_page.click()
# 控制页面回退
chrome_browser.back()
# 控制页面前进
chrome_browser.forward()
# 退出浏览器
chrome_browser.quit()

无界面浏览器:

通过前面的操作不难发现,使用真正的浏览器访问服务器,会渲染页面,导致资源浪费和效率降低。因此使用无界面浏览器,可以在访问网页时不渲染页面,并且达到同样的效果。使用 Chrome Headless 时有固定写法。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options


chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
# path是自己chrome浏览器的文件路径
chrome_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
chrome_options.binary_location = chrome_path
chrome_headless_browser = webdriver.Chrome(options=chrome_options)

也可以将固定的代码封装起来,需要使用的时候再调用。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 封装
def chrome_headless():
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    # path是自己chrome浏览器的文件路径
    chrome_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
    chrome_options.binary_location = chrome_path
    chrome_headless_browser = webdriver.Chrome(options=chrome_options)
    return chrome_headless_browser


browser = chrome_headless()
url = 'https://baidu.com/'
browser.get(url)
browser.save_screenshot('baidu.png')

# Requests

Requests: HTTP for Humans™,是专门为 Python 而生的 HTTP 库。它说它比 urllib 那些包都好。

安装(CMD)

pip install requests

使用方法:

import requests

url = 'https://baidu.com'
# 打开url,类似于urllib.request.urlopen(),但下面返回的是Response类型的对象。
response = requests.get(url)
# 设置响应的编码格式
response.encoding = 'utf-8'
# 字符串形式的response里的内容
response.text
# response的url地址
response.url
# 二进制形式的response里的内容
response.content
# 响应的状态码
response.status_code
# 响应头
response.headers

GET请求:

 urllib 相比,参数不需要使用 urlencode() 进行编码,也不需要定制请求对象,请求路径中最后的 ? 可以不加。

import requests


url = 'https://www.baidu.com/s'
para = {
    'wd': '测试'
}
headers = {
    'User-Agent': 'Mozilla/5.0'
}
response = requests.get(url=url, params=para, headers=headers)
# 设置编码
response.encoding = 'utf-8'
# 输出response编码后的内容
print(response.text)

POST请求:

下面的案例还是使用的百度翻译。与 urllib 相比,不需要进行请求对象定制。

import requests

import json

url = 'https://fanyi.baidu.com/sug'
data = {
    'kw': 'test'
}
headers = {
    'User-Agent': 'Mozilla/5.0'
}
# 与get请求不同,post请求的参数用data=传入
response = requests.post(url=url, data=data, headers=headers)
# 因为得到的是json格式的数据,所以使用json.loads()进行自动编码并打开
json_content = json.loads(response.text)

代理:

使用 proxies= 传入代理。

proxy = {
    'http': '127.0.0.1:1234'
}
response = requests.post(url=url, data=data, headers=headers, proxies=proxy)

Cookie登陆:

想要模拟登陆,首先需要找到登陆的接口。要想找到登陆的接口,可以先使用错误的账号密码进行登陆,再在 Network 里找到登陆的接口。

可以看到 Form Data 里除了 账户及密码 等固定变量,还有 __VIEWSTATE及验证码 等随机变量。而除了验证码外的随机变量,一般都可以在网站的源代码中找到。所以模拟登陆之前应该在网站的源代码中找到这些随机变量的值。

至于验证码,一般的思路是使用 urllib.request  urlretrieve() 方法将验证码图片下载到本地,但在实际操作中不可行,因为再次访问验证码链接时验证码会变。所以在此使用 requests  session() 方法,将请求变为对象,再使用该对象获取验证码并进行登陆( session.post() ),而不使用 requests.post() 进行登陆。

需要注意的是,使用 requests.session() 获取的验证码图片数据,需要使用二进制的方式保存到本地。

以二进制格式打开一个文件只用于写入

open(‘code_pic.png’, ‘wb’)

以下是一个案例:

import requests
from bs4 import BeautifulSoup


login_page_url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'
headers = {
    'user-agent': 'Mozilla/5.0'
}
login_session = requests.session()
# 通过session访问登陆页面并获取响应源码
response = login_session.get(login_page_url)
# 使用BeautifulSoup来解析网页源码,以获取登陆时需要的随机变量
soup = BeautifulSoup(response.text, 'lxml')
VIEWSTATE_code = soup.select('input[id="__VIEWSTATE"]')[0].attrs.get('value')
VIEWSTATE_GENERATOR_code = soup.select('input[id="__VIEWSTATEGENERATOR"]')[0].attrs.get('value')
captcha_code_response = login_session.get("https://so.gushiwen.cn/RandCode.ashx")
# 注意,图片需要使用二进制格式来写入
with open('captcha.png', 'wb') as captcha_file:
    captcha_file.write(captcha_code_response.content)
captcha_code = input("请输入验证码:")
post_data = {
    '__VIEWSTATE': VIEWSTATE_code,
    '__VIEWSTATEGENERATOR': VIEWSTATE_GENERATOR_code,
    'from': 'http://so.gushiwen.cn/user/collect.aspx',
    'email': '111',
    'pwd': '222',
    'code': captcha_code,
    'denglu': '登录'
}
login_url = 'https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx'
login_response = login_session.post(url=login_url, data=post_data, headers=headers)
# 写入时进行编码
with open('login_gushiwen.html', 'w', encoding='utf-8') as gushiwen:
    gushiwen.write(login_response.text)

打码平台:

刚才的案例中需要手动输入验证码,使用打码平台可以免除这个步骤,让打码平台来识别验证码。

# Scrapy

企业级爬虫框架,用来提取结构性数据。能被应用在数据挖掘、信息处理等一系列操作中。

安装:

 Scrapy 的安装较为复杂,首先需要更新 pip ,再安装对应的 Twisted 框架,之后再在 CMD 安装 Scrapy 。如果提示 win32 的错误,则安装 pypiwin32 库。

CMD

python -m pip install --upgrade pip
pip install Twisted
pip install scrapy

使用方法:

创建 Scrapy 项目时需要在 CMD 中创建。

创建项目

scrapy startproject 项目名

注:项目名不允许以数字开头,也不能包含中文。

依旧使用 CMD 进入创建好的文件里的 spiders 文件夹,创建 spider 文件。

CMD:
# 创建spider文件(在spiders目录下)
scrapy genspider 文件名 要获取的网页
scrapy genspider baidu www.baidu.com

# 执行(CMD在spiders目录下)
scrapy crawl baidu

 settings.py 中把 ROBOTSTXT_OBEY 注释掉,才能正常执行。

项目结构:

1.项目结构
    项目名字
        项目名字
            spiders文件夹
                __init__.py
                自定义.py    #核心功能文件
            __init__.py
            items.py        #定义数据结构,获取那些结构的数据
            middlewares.py  #中间件,代理
            pipelines.py    #管道,用来处理下载的数据
            settings.py     #配置文件,有robots协议ua定义等 

response:

 自定义.py 里有一个 response 变量。

import scrapy


class BaiduSpider(scrapy.Spider):
    name = 'baidu'
    allowed_domains = ['www.baidu.com']
    # 如果url以html结尾的话,要去掉最后的/
    start_urls = ['http://www.baidu.com/']

    def parse(self, response):
        # 响应的字符串形式
        content = response.text
        # 响应的二进制形式
        content = response.body
        # 使用XPath
        span = response.xpath('XPath语句')[0]
        # 提取selector对象的data属性值
        span.extract()
        # 提取selector列表的第一个数据
        span.extract_first()

有关 Scrapy 的功能结构和更多用法,可以观看尚硅谷P93-P104.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments