Both the compiler and the assembler take a human readable text and turn it into a binary object of a specific format.
The difference is mainly in:
The direction the specifics come from.
In a compiler the specifics come from above, from the programming language it is targeting.
High-level languages are abstract in the sense that they are designed with an abstract machine in mind, they hide the hardware details.
The compiler has to map the operation in the abstract machine into operation in targeted machine (a real machine).
In an assembler the specifics come from below, from the ISA it is targeting.
Assembly is concrete (not 100% concrete, see Pseudo-instructions for example), it is a mnemonic for the opcodes of a specific CPU.
The assembler must allow the programmer to work with mnemonic as they'd work with the opcodes since the goal is to instruct the CPU directly so there is a close 1:1 mapping between assembly instructions and machine instructions.
Complexity
High-level languages have a complex syntax that tends to be similar to english, the parsing and the mapping the compiler has to do is quite complex.
A lexer/tokenizer and a parser are needed to create an AST that is used to generate the machine code accordingly and this requires keeping a context between the nodes of the tree.
We also expect compilers to optimise the AST and the generated code.
Assembly has a line-based syntax, the lexer and the parser are limited to a line at a time and can often be combined and accomplished with a smart table lookup.
No complex state management.
There is practically no room for optimisation and no fancy, hard-to-implement, feature to map into the machine code, actually the mapping is straightforward as it has already been done by the programmer.
Bot compilers and assembler need to support some object and execution file format so some complexity creeps in.
Goals
The compiler is a tool that fulfils the goal of abstracting the hardware detail away from its user, we want to write a source code that can run, when recompiled, in any hardware ideally.
The assembler is a tool that comes handy when one doesn't want to abstract the hardware away but rather they want to fully exploit its features.
So the assembler will expose a set of low-level details (e.g. segments) that a compiler will, instead, try to hide.
We can think of a compiler as an human who follow a cooking receipt, when it says "mix the milk" the human has to actually take the right tool (a wooden spoon?), put it in the milk and make a rotatory movement.
That's a complex thing.
An assembler is like a child, it won't understand "mix the milk" we have to tell it "take the wooden spoon from the drawn on the left, it's like a long stick made of wood with a convex end" then, "Hold the container still with and hand", "Put the wooden spoon 4-5 inches deep in the milk", "make a circle, going steady and not too fast, clockwise", "repeat for 20 seconds".
These are easier instruction to parse and at the same time allows the instructor to have more control over the whole operation, e.g. if they wanted to change how deep the spoon goes.
So why do we have assemblers?
Because we need a way to generate machine instruction precisely, we could use a compiler for that, with a specific language but since a list of low-level instructions is way less structured than an abstract operation, the end result would be a glorified assembler (more or less like having a versioning software like Git just to commit a working dir full of copy-pasted backups).
So we keep them separated: simple structure for the assembler, complex grammars for the compiler.