There are several ways to do it.
First consider if you want to implement the powerup code within the powerups or within the player. A rule of thumb is: if the powerup can affect more actors in the same way, it might make sense to have the code inside powerups, to avoid duplication. If the powerup can affect multiple actors but in different ways, the actors should implement it separately. If powerups can only ever affect one actor, it really depends on the context of your project architecture.
If you want to implement the collision on the player end, use your method of choice for collision detection (for KinematicBody use the KinematicCollision2D provided by the movement methods; for a player whose collision is implemented via Area2D, simply connect the area_enter signal). To identify weather the colliding area is indeed a powerup, you could set the powerups' class_name and then:
func _on_collision(body: Area2D):
if body is Powerup:
You could also use groups -
if body.is_in_group("powerups"):, or object metadata -
A similar approach could be done on the powerup side; you could either have the spawner connect your collision directly to the player (assuming the spawner has a direct reference to the player), however this could be bad practice unless you absolutely know for sure that there is only ever going to be one and only player. Alternatively you could have the powerup's area scan for any incoming body_entered or area_entered signals, identify the collisionee and act accordingly.
To better understand possibilities of inter-object communication, you might want to read the scene organization best practices article from the docs.
Edit: hopefully fixed formatting