1,先进入虚拟环境(虚拟环境中下载好了scrapy)
#创建工程
scrapy startproject ArticleSpider
#生成爬虫模板
cd ArticleSpider
scrapy genspider wenzhang duwenzhang.com
2,在Pycharm中选择解释器
3,进行调试,新建main文件
<1>启动爬虫
scrapy crawl wenzhang
发现缺少win32api模块(window下会出现)
Pip install –i https://pypi.doubanyuan.com/simple/ pypiwin32
<2>main.py
from scrapy.cmdline import execute
import sys
import os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(["scrapy","crawl","wenzhang"])
《1》execute函数可执行python文件
《2》sys,需要设置项目目录,scrapy在该目录下执行。
sys.path指定模块搜索路径的字符串列表
《3》os.path模块,用来访问文件或路径。
os.path.dirname()返回某个文件的文件夹
os.path.abspath()返回文件的绝对路径
__file__当前文件(即main文件)
<3>settings配置
#默认是遵循robots协议的,我们要将其注释掉,如不设置,scrapy会默认读取每一个网站上的robots协议,把不符合robots协议的url过滤掉
ROBOTSTXT_OBEY = False
<4>在main文件中进行debug调试
使用Scrapy shell下载指定页面的时候,会生成一些可用的对象,比如Response对象和Selector对象
1,简介
使用路径表达式在xml或html中定位元素
2,简单的表达式说明
article 选取所有article元素的所有子节点
/article 选取根元素article
article/a 选取所有属于article子元素的a元素
//div 选取所有div元素
article//div 选取所有属于article元素的后代div元素
//@class 选取所有名为class的属性
/div/* 选取属于div元素的所有子节点
//div/a|//div/p 选取是由有div元素的a和p元素
/article/div[1] 选取属于article子元素的第一个div元素
/article/div[last()]选取article子元素的最后一个div元素
//div[@lang] 选取所有拥有lang属性的div元素
eg:
response.xpath('//td[@class="daohang"]/a[1]/text()')
xpath(“//span[contains(@class,’vote-post-up’)]”)
3,注意:路径和审查的源码可能是不一样的,因为页面显示可能是经过js处理后的,查看网页源代码更准确
简单的表达式说明
#container 选择id为container的节点
.container 选择所有class包含container的节点
li a 选择所有li下的所有a节点
ul+p 选择ul后面的第一个p元素
a[title] 选取所有有title属性的a元素
a[href='xx'] 选取所有href属性为xx值的a元素
a[href^='xx'] 选取所有href属性属性值以xx开头的a元素
li:nth-child(3) 选取第三个li元素
# -*- coding: utf-8 -*-
from typing import Optional, Match
import scrapy
import re
from scrapy import Request
from urllib import parse
import datetime
from ArticleSpider.items import ArticleItem,ArticleItemLoader
from ArticleSpider.utils.common import get_md5
class WenzhangSpider(scrapy.Spider):
name = 'wenzhang'
allowed_domains = ['tendcode.com']
start_urls = ['https://tendcode.com/']
def parse(self,response):
#获取文章列表页url,交给并交给scarpy下载
post_list = response.css(".media")
for post in post_list:
post_image_url = post.css("aimg::attr(src)").extract_first("")
post_url = post.css("a ::attr(href)").extract_first()
yield Request(url=parse.urljoin(response.url,post_url),meta={"post_image_url":parse.urljoin(response.url, post_image_url)},callback=self.parse_detail)
#提取下一页,交给scrapy进行下载
nex_url = response.css(".text-success::attr(href)").extract_first()
if nex_url:
yield Request(url = parse.urljoin(response.url, nex_url),callback=self.parse)
def parse_detail(self, response):
article_item = ArticleItem()
post_image_url = response.meta.get("post_image_url","")
item_loader = ArticleItemLoader(item=ArticleItem(),response=response)
item_loader.add_css("title","h1::text")
item_loader.add_css("pinglun",".f-13 a::text")
item_loader.add_css("time",".f-13 span::text")
item_loader.add_css("tags",".tag-cloud a::text")
item_loader.add_value("url_object_id",get_md5(response.url))
item_loader.add_value("post_image_url",[post_image_url])
article_item = item_loader.load_item()
#会传递到pipline中
yield article_item
代码分析:
<1>allowed_domains
一个可选的字符串列表,其中包含允许此爬行器爬行的域
start_urls
爬虫开始的url列表
<2>查询响应可以使用xpath和css选择器
<3>为了提取文本数据,你必须使用selector对象的get()和getall()方法
如果想提取第一个匹配的元素,可以使用.get(),或者.extract_first()get()总是返回单个结果,如果有多个匹配,返回第一个
getall()返回结果列表,等同于extract()
如果用数组下标来取,就必须考虑到数组为空的异常情况,
extract_first():这个方法返回的是一个string字符串,是list数组里面的第一个字符串,提取不到返回空,但不会报错
<4>选择标签里的文本
xpath中text()
css中::text
<5>选择属性
css中::attrib它返回第一个匹配元素的属性,::attr返回列表(::attr(src))
xpath中,/@src
<6>Request
scrapy使用Request和Response对象来爬取网站
通常,Request对象在spider中生成,并在整个系统中传递,直到它们到达Downloader, Downloader执行请求并返回一个Response对象
一参,请求的url,二参,下载后调用的函数
<7>通过yield发起请求
<8>urljoin
在页面中,文章链接可能不是完整的url,是相对的
连接基本URL和可能的相对URL以形成绝对URL
<9>self.parse
scrapy底层是基于twist框架来完成的,twist会根据我们的函数名自动调用我们的函数
<10>item
item对象是简单容器,用于收集收集到的数据
<11>下载图片
配置:
重点1!!!IMAGES_URLS_FIELD 图片下载地址为该字段(post_image_url)
记得时刻检查settings里的图片下载字段是否匹配!!!!!
不在settings 中配置 IMAGES_URLS_FIELD字段需要复写get_media_requests
def get_media_requests(self, item, info):
image_link = item['post_image_url']
yield scrapy.Request(image_link)
IMAGES_STORE图片的保存位置
IMAGES_URLS_FIELD = "post_image_url"
project_dir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir, 'images')
重写imagePipeline(item_completed获得图片的本地路径)
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
#知乎中没有post_image_url
if "post_image_url" in item:
for ok,value in results:
image_file_path = value["path"]
item["post_image_path"] = image_file_path
return item
item_completed当下载完成时,结果会被发送到item_completed方法,其中results参数可以获得我们需要的
[(True,
{'checksum': '2b00042f7481c7b056c4b410d28f33cf',
'path': 'full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg',
'url': 'http://www.example.com/files/product1.pdf'}),
(False,
Failure(...))]
pipeline里边的数字是处理顺序,越小处理越早
ImagesPipeline实现图像缩略图生成
item_completed可以获得下载地址
重点2!!!下载图片时,会报一个错,raise vale error
get_media_requests方法(定义要下载图片的url)要的是数组,那么我们保存图片时,也写成数组
article_item[“post_image_url”] = [post_image_url]
爬下一页的时候爬不到?
在start_urls哪里没有加/
<12>对url进行md5加密,让其长度固定
新建一个文件夹utils,专门用来放常用的函数
使用md5()创造一个md5哈希对象,对要加密字节使用update方法,用hexdigest()方法拿出
import hashlib
def get_md5(url):
if isinstance(url,str):
url =url.encode("utf-8")
m = hashlib.md5()
m.update(url)
return m.hexdigest()
不支持将字符串作为对象输入update,因为Hash作用于字节而不是字符
python3将python2中的字符都变成了unicode,应先转化成utf-8